头图

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 

PingCode研发中心
111 声望22 粉丝