Angular v17带来了新的控制流语法!我试了一下迁移命令
这篇文章是 Angular Advent Calender 2023 第6天的文章。
第5天的文章是rch850先生使用Angular 17的View Transitions API进行玩耍。
Angular v17带来了新的控制流语法!
在过去的Angular版本中,作为模板功能的“分支”和“循环”是通过结构型指令(ngIf、ngFor、ngSwitch)来实现的。
从 Angular v17 开始,将新增控制流语法 @if、@for 和 @switch,旧有的结构型指令可以全部替换为新的控制流语法。
只需要一种选项:
详细信息可参阅官方文件或者
如果你能阅读Angular Advent Calender 2023第二天的文章,了解carimatics关于引入嵌入式控制流以及其背景的贡献,那么你也能理解我的动机,并且会很好。
将迁移命令重写为新的控制流语法
Angular团队已经准备了一个迁移命令,用于将现有的结构性指令转换为新的控制流程语法。
ng generate @angular/core:control-flow
这个命令真方便,一键搞定!
我们立刻来试一试吧。以以下实施为例,
<ng-container *ngIf="greeting">
<span>hello</span>
</ng-container>
<ng-container *ngFor="let number of numbers">
<span>{{ number }}</span>
</ng-container>
<ng-container [ngSwitch]="num">
<span *ngSwitchCase="1">one</span>
<span *ngSwitchCase="2">two</span>
<span *ngSwitchCase="3">three</span>
</ng-container>
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
greeting: boolean = true;
numbers: number[] = [1,2,3,4,5];
num: number = 2;
}
执行命令。
$ ng generate @angular/core:control-flow
? Which path in your project should be migrated? ./
? Should the migration reformat your templates? Yes
IMPORTANT! This migration is in developer preview. Use with caution.
UPDATE src/app/app.component.html (248 bytes)
UPDATE src/app/app.component.ts (366 bytes)
您可以在对话框中指定路径和选择重新格式化。实际的差异如下所示。
$ git diff -w src
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 781f3e5..2aef72f 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,11 +1,17 @@
-<ng-container *ngIf="greeting">
+@if (greeting) {
<span>hello</span>
-</ng-container>
-<ng-container *ngFor="let number of numbers">
+}
+@for (number of numbers; track number) {
<span>{{ number }}</span>
-</ng-container>
-<ng-container [ngSwitch]="num">
- <span *ngSwitchCase="1">one</span>
- <span *ngSwitchCase="2">two</span>
- <span *ngSwitchCase="3">three</span>
-</ng-container>
+}
+@switch (num) {
+ @case (1) {
+ <span>one</span>
+ }
+ @case (2) {
+ <span>two</span>
+ }
+ @case (3) {
+ <span>three</span>
+ }
+}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 9a57e6b..4bfd92f 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,11 +1,11 @@
import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
+
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
- imports: [CommonModule, RouterOutlet],
+ imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
你已经做出很好的改变了。
对于要求使用 “@for” 指定的 track,应该直接指定循环变量本身。这里需要对其进行修正,以确保成为唯一的属性。
+@for (number of numbers; track number) {
我也注意到了少了 CommonModule 的引用,看来是有意想要减小文件大小的意愿呢。
- imports: [CommonModule, RouterOutlet],
+ imports: [RouterOutlet],
我尝试在实际生活中的业务代码中进行测试。
好了,接下来进入正题。
重要!此迁移正在开发者预览中,请谨慎使用。
既然在执行时会产生这样的输出,我便很好奇在实际的业务代码中,它能以多大的精度替代。让我们立即试一试。
虽然无法公开具体业务代码,但我可以大致告诉你它的规模。
# コンポーネント数
grep -r '@Component' src | wc -l
156
# テンプレートファイルの行数
find src -name "*.html" -type f | xargs cat | wc -l
22403
# ngIfの総数
grep -r '*ngIf' src | wc -l
360
# ngForの総数
grep -r '*ngFor' src | wc -l
106
# ngSwitchの総数
grep -r 'ngSwitch' src | wc -l
50
从第一个提交开始已经过去了三年多,现在仍然是一个经验丰富的项目。
看看执行迁移后的差异结果
执行顺利完成,没有出现任何特别的错误。
我对指定了ngFor的trackBy后会发生什么变化感到好奇,所以进行了确认。
-<ng-container *ngFor="let option of options; trackBy: trackByOption">
+@for (option of options; track trackByOption($index, option)) {
看起来已经进行了很好的转换。不错呢。
我大致看了一遍,但没有特别值得一提的内容,只是进行了一些改写。
在构建过程中发生错误的情况
在尝试构建时,发现了两种导致构建错误的情况。
错误案例1
错误 TS2348: 类型 ‘typeof of’ 的值不可被调用。您是不是想要包含 ‘new’?
由于在 *ngFor 的变量中使用了保留词 class,造成了以下问题的原因。
<ng-container *ngFor="let class of classes">
<span>{{ class }}</span>
</ng-container>
这个版本可以通过构建,但是修改后的版本会出现构建错误。
@for (class of classes; track class) {
<span>{{ class }}</span>
}
构造指令的语法与控制流语法的评估方式似乎有所不同(考虑到更严格地应用类型检查,这是可以理解的)。如果被告知在ngFor循环变量中使用保留字是错误的,那确实如此。如果不小心在ngFor循环变量中使用保留字,可能会遇到问题。
错误案例2
错误NG5002:@switch块只能包含@case块和@default块。
在 ngSwitch 的容器下方存在 ngIf 的情况下,出现了故障。
其实我自己也不太明白自己在写什么,而且对于为什么会变成这样的实现方式也感到很迷惑,但是在现有的业务代码中,有很多东西要考虑的呢…
以下是具体的代码。
<ng-container [ngSwitch]="num">
<ng-container *ngIf="greeting">
<span>hello</span>
</ng-container>
</ng-container>
无论实施目的如何,上述代码实际上可以成功编译通过。如果将其迁移,结果将如下所示。
@switch (num) {
@if (greeting) {
<span>hello</span>
}
}
看起来在 @switch 块里不能放置 @if 块呢。
话虽如此,根本不应该写出这样的代码!而且,通常人们也不会写这样的代码,一般情况下也不需要担心。如果能够以趣味性的方式记住这个知识点就太好了。
总结
通过迁移命令进行修改后,已修复上述错误情况的应用程序成功构建,并且目前在验证环境中正常运行。太棒了!
只要代码不是过于混乱到让人束手无策,对于自动测试已经相当完善的项目,在迁移命令中使用机械化的重写方式,再通过自动测试来进行确认,感觉应该是可以的。
使用新的控制流语法不仅可以提升开发体验,还能提高应用程序的性能。我们应该积极进行重写。
明天是beltway7先生。请多关照!