2023年11月8日 Angular 团队发布了 Angular 17 开发预览版,在新的版本,Angular 添加了许多激动人心的特性,其中就包含新的控制流和延迟视图新版 Angular 增加一个Block
的概念, Block
是模板中的一种新语法结构,控制流和延迟视图也是基于这种语法结构来实现的。
控制流
什么是控制流?
控制流是指程序中决定语句执行顺序的机制。它通过顺序、选择(条件判断)、循环等结构,使程序能够根据不同条件或规则执行不同的代码块,实现灵活的逻辑控制,代码中常见的if-else
for
都属于控制流语法。
Angular 中的控制流
Angular 16 及之前的版本中的控制流是基于微语法和结构指令来实现的,比如:
问题
- 不够直观简洁(目前模板中基于指令的控制流会使模板代码变的冗长,这种冗长会导致降低代码的可读性)
- 灵活度差(微语法模型也不支持控制流语句的多个关联子模板,所以 Angular 无法解决
*ngIf
的情况else
使用起来非常尴尬的问题) - 类型检查不友好
- 独立组件需要引入
新的控制流
关注 Angular 的同学了解,Angular 一直在推动 Zoneless 和 Singals 的工作,Angular 目前的控制流指令是无法在 Zoneless 的应用中运行的,在考虑修改现有指令以支持 Zoneless 应用时,Angular 团队决定不这样做,因为潜在的重大更改(需要兼容旧的应用程序)和代码复杂性增加。相反,他们借此机会选择引入一种新的内置控制流语法,该语法既支持 Zoneless 应用程序,又解决微语法长期存在的 DX(开发体验)问题。
语法
官方在 RFC 时候提出了 #-syntax
类似 HTML 的标签语法,如 {#if}
、 {:else}
和 {/if}
{#if cond.expr} Main case was true!{:else if other.expr} Extra case was true!{:else} False case!{/if}
不过在 RFC 讨论阶段,大部分人更喜欢 @-syntax
的语法,最终 Angular 团队通过调查和研究发现,大部分人也都认可这个方案,最终确定了使用了 @-syntax
的语法
`
@if (cond.expr){ Main case was true!} @else { False case!}
使用IF-ELSE新的条件控制使用 `@if` 和 `@else` 块来定义,与旧的 `*ngIf `语法相比,新的控制流同时还支持了 ”else if“ 的能力
![图片](https://atlas.pingcode.com/files/public/6578739dea1029ea54ca6eac)
新的语法依旧支持通过 `async` 订阅
![图片](https://atlas.pingcode.com/files/public/65787472ea1029ea54ca6eaf)
#### Switch
新的 Switch 与 `@switch` `@case ` `@default` 三个块组成,支持直接在块中传入内容,新的这种使用方式使我们的代码更清晰直观
![图片](https://atlas.pingcode.com/files/public/657875e8ea1029ea54ca6eb0)
#### For Loop
新的 for 循环我认为变化最大的,在语法上,新的 `@for` 简化了 `*ngFor `的使用,不需要写 `let` ,同时直接直接指定 `track` 属性,在性能上,Angular 也做了比较大的优化,同时还内置支持了 empty 场景。
![图片](https://atlas.pingcode.com/files/public/65787686ea1029ea54ca6eb1)
新的语法依旧支持通过 `async` 订阅
![图片](https://atlas.pingcode.com/files/public/65787768ea1029ea54ca6eb2)
内置的隐藏变量依旧与 *ngFor 保持一致
![图片](https://atlas.pingcode.com/files/public/655f129261365a271b665fd4)
### 延迟视图
在 Angular 16 中如何实现延迟加载组件?
![图片](https://atlas.pingcode.com/files/public/65787f55ea1029ea54ca6eb6)
#### @defer
Angular 支持通过 Router 延迟加载应用程序的某些部分(延迟路由),单个组件的延迟加载可以通过 dynamic `import()` 和 `ngComponentOutlet` 实现,但这种方法可能很复杂且容易出错,因此 Angular 17 在核心框架中引入一种更符合人体工程学的延迟加载组件的方法 `@defer` , ` @defer` 不仅仅支持延迟组件,同时也支持延迟加载指令和管道,随着新的 `@defer` 的引入,我们可以更细颗粒度的控制我们的加载资源,优化应用初始包的体积,提升用户加载的速度。
#### 使用限制
* `@defer `块中的组件必须是独立组件,非独立组件不支持延迟加载并且会立即加载,即使它被包裹在 `@defer` 块中
* 不能在 ` @defer` 块以外引用这个组件,包括使用 `@ViewChild` 查询这个组件
#### 使用@defer
`@defer` 块中的内容最初不会显示,当满足指定的触发器或条件并获取依赖项,块中的内容才会展示,默认情况下,当浏览器状态变为空闲状态(Idle)时,会触发 ` @defer` 加载。
![图片](https://atlas.pingcode.com/files/public/65787f7fea1029ea54ca6eb7)
#### @placeholder
默认情况下, `@defer ` 块在触发之前不会呈现任何内容,我们可以定义 `@placeholder` 可选的块,声明在触发延迟块之前要显示的内容,当延迟内容加载完成后, `@placeholder` 块中的内容会销毁,需要注意的是在 ` @placeholder` 块中的内容永远都是立即记载的
![图片](https://atlas.pingcode.com/files/public/65787f90ea1029ea54ca6eb8)
`@placeholder` 块接受一个可选参数 `minimum` 来指定应显示此占位符的时间,单位支持 s 和 ms。` minimum `是为了防止延迟项加载过快导致内容闪烁。
![图片](https://atlas.pingcode.com/files/public/65787fa3ea1029ea54ca6eb9)
#### @loading
`@loading` 与 `@placeholder` 类似,也是是一个可选块, `@loading` 和 `@placeholder` 区别是 `@loading` 块中指定的内容只有当资源加载时候才会展示,在资源未被加载之前永远展示的都是 `@placeholder` ,`@loading` 块支持接收两个参数 ` after` 和 ` minimum `
![图片](https://atlas.pingcode.com/files/public/65787fb4ea1029ea54ca6eba)
#### @error
`@error` 比较好理解,我们可以在 `@error` 中指定依赖加载失败时候展示的内容,与 `@placeholder` 和 `@loading` , `@error` 块的内容也是立即加载的,并且也是可选的
![图片](https://atlas.pingcode.com/files/public/65787fcdea1029ea54ca6ebb)
#### 触发器 Triggers
默认情况下,当浏览器状态变为空闲状态(Idle)时,会触发 `@defer` 加载,不过我们也可以根据自己的需求指定其他的触发器,Angular 提供了两种触发方式 `When` 和 `On`
#### When 条件触发
指定一个条件,当满足这个条件时触发
![图片](https://atlas.pingcode.com/files/public/65787fdeea1029ea54ca6ebc)
#### On 支持以下几种内置的触发器
on idle (浏览器闲时触发,利用浏览器的`requestIdleCallback` 特性)
![图片](https://atlas.pingcode.com/files/public/6583ddf8ea1029ea54ca77fc)
on immediate(应用渲染完后立即触发)
![图片](https://atlas.pingcode.com/files/public/6583de1bea1029ea54ca77fd)
on timer(指定一个时间间隔后触发)
![图片](https://atlas.pingcode.com/files/public/6583de27ea1029ea54ca77fe)
on viewport(Placeholder 或指定元素进入可视区域后触发,利用的 `IntersectionObserver` 特性,组员保证 Placeholder 必须是一个DOM节点)
![图片](https://atlas.pingcode.com/files/public/6583decbea1029ea54ca7800)
on hover(Hover Placeholder 或者 Hover 指定某个区域触发)
![图片](https://atlas.pingcode.com/files/public/6583defcea1029ea54ca7801)
on interaction(Placeholder 或者指定元素触发 click 或者 keydown 事件时触发)
![图片](https://atlas.pingcode.com/files/public/6583df54ea1029ea54ca7803)
On 和 When 可以组合使用
![图片](https://atlas.pingcode.com/files/public/65788010ea1029ea54ca6ebe)
#### 预获取资源 Prefetch
`@defer` 允许指定何时触发依赖项的预取条件,我们可以使用 `prefetch` 关键字, `prefetch` 可以 `when` 和/或 ` on` 结合声明触发器
![图片](https://atlas.pingcode.com/files/public/6578801eea1029ea54ca6ebf)
#### 一些注意事项
* `@defer` 块中如果不包含任何依赖项(组件、指令、管道)等,则这个 `@defer` 块是无效的
* `@defer` 块与 `ng-content` 结合使用时,外部投影的内容不会延迟加载,如果要延迟加载投影的内容,可以在外部将投影的内容单独包装到 `@defer` 块中
* `@defer` 支持嵌套,我可能指定进入可视区域时渲染一个大组件,然后用户Hover某个元素时渲染一个内部子组件,不过需要注意尽量避免这样的使用,可能会导致性能问题
#### 编译结果
编译后, `@defer` 块中的组件会打包成一个一个 chunk 文件用于单独的加载
![图片](https://atlas.pingcode.com/files/public/65608ea161365a271b666112)
#### 测试
Angular 提供 TestBed API 来简化测试块和在测试过程中触发不同状态的过程 `@defer` 。默认情况下,` @defer` 测试中的块是“暂停”的,我们可以手动在状态之间转换
![图片](https://atlas.pingcode.com/files/public/65788032ea1029ea54ca6ec0)
#### 插件支持
**Prettier**
` npm i prettier@3.1 --save-dev`
QAQ:控制流会支持自动迁移吗?
A:Angular 将会提供 Schematics 工具自动迁移
Q:新的控制流会不会影响 ViewChild 查询结果?
A:不会,与原有的结构指令行为一致
Q:现有结构指令会废弃吗?
A:不会,结构指令是 Angular 中应用程序架构的一个基本功能,Angular 没有计划删除它们
Q:未来支持自定义 Block 吗?
A:暂时不会,未来可能会
Q:在 @defer 可以定义自己的 on 的触发器吗?
A:不可以,可以用 When 来实现自定义触发器
Q:CDK 的虚拟滚动会收到影响吗?
A:CDK 目前将继续为虚拟滚动和其他用例提供其现有的结构指令。Angular 将研究将 CDK 的一些结构指令转换为内置语法,或者将扩展点添加到现有语法中供 CDK 构建(例如,支持虚拟`for`滚动)
#### 引用
* https://angular.dev/guide/defer
* https://github.com/angular/angular/discussions/50719
* https://github.com/angular/angular/discussions/50716
* https://medium.com/@ayushgrwl365/introducing-new-control-flow-in-angular-17-89e67903daed
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。