将装饰器迁移到输入、查询和输出函数
input()、output()、viewChild()、viewChildren()、contentChild()、contentChildren()、outputFromObservable() 和 outputToObservable() 在 Angular 19 中处于稳定状态。生产应用程序可以开始使用 Angular 原理图将装饰器转换为信号。
Angular 19 将信号输入、输出和查询迁移合并为单个信号迁移。开发人员可以打开终端,执行原理图,然后从文本菜单中选择一个或所有迁移。
让我们在这篇博文中逐步进行迁移。
您可以在此功能分支中找到装饰器版本:https://github.com/railsstudent/ithome2024-demos/tree/refactor/day40-decorators/projects/day40-migration-schematic-demo
示例 1:迁移到信号输入和新输出
@Component({ selector: 'app-some', standalone: true, imports: [FormsModule], template: ``, }) export class SomeComponent { @Input({ required: true, alias: 'backgroundColor'}) bgColor!: string; @Input({ transform: (x: string) => x.toLocaleUpperCase() }) name: string = 'input decorator'; }bgColor: {{ bgColor }}
name: {{ name }}
`SomeComponent` 组件使用 `Input` 装饰器从父组件接收输入。`bgColor` 是必需输入,别名为 `backgroundColor`。`name` 是将传入值转换为大写的输入。
export class SomeComponent { @Output() triple = new EventEmitter(); @Output('cube') powerXBy3 = new EventEmitter (); numSub = new BehaviorSubject (2); @Output() double = this.numSub.pipe(map((n) => n * 2)); }
同一个组件还有三个应用了 `Output` 装饰器的事件发射器。`triple` 是一个发出数字的事件发射器。`powerXBy3` 也是一个别名为 `cube` 的数字事件发射器。`double` 使用 `numSub` behaviorSubject 的值,乘以 2,并将结果发射到父组件。
在终端中运行 Angular 信号示意图来迁移演示。
将输入装饰器迁移到信号输入
ng g @angular/core:signals
在选择菜单中,选择所有迁移并继续下一步。
信号输入迁移后,
readonly bgColor = input.required({ alias: "backgroundColor" }); readonly name = input ('input decorator', { transform: (x: string) => x.toLocaleUpperCase() });
对于 `bgColor`,使用 alias 选项调用 `input.required` 函数。对于 `name`,使用初始值和 transform 选项调用输入,以使输入文本大写。迁移对父组件没有影响。
@Component({ selector: 'app-some', standalone: true, imports: [FormsModule], template: `bgColor: {{ bgColor() }}
name: {{ name() }}
`, }) export class SomeComponent {}`bgColor` 和 `name` 是信号输入;HTML 模板调用信号函数来显示它们的值。
输出迁移后,
readonly triple = output(); readonly powerXBy3 = output ({ alias: 'cube' }); numSub = new BehaviorSubject (2); double = outputFromObservable(this.numSub.pipe(map((n) => n * 2))); `triple` 调用输出函数,发出一个数字。`powerX3` 调用输出函数,并将别名选项作为第一个参数传递。别名的值是 `cube`,与之前相同。`double` 使用 `outputFromObservable` 函数将 Observable 转换为输出。迁移对父组件也没有影响。
将 OutputRef 转换为输出
num = signal(2); double = output(); updateNum(value: number) { this.num.set(value); this.double.emit(value * 2); } `numSub` BehaviorSubject 转换为信号,`double` OutputRef 转换为输出函数。我定义了一个 `updateNum` 方法来覆盖 `num` 信号并将该值发送到 `double` 自定义事件。
Num:新值不会发送给“numSub”主题,而是传递给“updateNum”方法。
示例 2:迁移查询修饰器
export class QueriesComponent implements AfterContentInit { @ContentChild('header') header!: ElementRef; @ContentChildren('p') body!: QueryList >; appendHeader = ''; list = ''; ngAfterContentInit(): void { this.appendHeader = `${this.header.nativeElement.textContent} Appended`; this.list = this.body.map((p) => p.nativeElement.textContent).join('---'); } } `QueriesComponent` 组件应用 `ContentChild` 和 `ContentChildren` 装饰器来查询投射到 `` 元素。
export class AppComponent implements AfterViewInit { @ViewChild(QueriesComponent) queries!: QueriesComponent; @ViewChildren('a') aComponents!: QueriesComponent[]; viewChildName = ''; numAComponents = 0; ngAfterViewInit(): void { this.viewChildName = this.queries.name; this.numAComponents = this.aComponents.length; } }`AppComponent` 组件使用 `ViewChild` 装饰器查询 `QueriesComponent` 的第一次出现。它使用 `ViewChildren` 装饰器查询与模板变量 a 匹配的所有 `QueriesComponent` 组件。
将查询修饰器迁移到查询函数
查询迁移后,
export class QueriesComponent implements AfterContentInit { readonly header = contentChild.required>('header'); readonly body = contentChildren >('p'); appendHeader = ''; list = ''; ngAfterContentInit(): void { this.appendHeader = `${this.header().nativeElement.textContent} Appended`; this.list = this.body().map((p) => p.nativeElement.textContent).join('---'); } } Appendheader: {{ appendHeader() }}List: {{ list() }}该原理图将 `ContentChild` 装饰器迁移到具有正确类型的 `contentChild` 函数。同样,该原理图将 `ContentChildren` 装饰器迁移到 `contentChildren` 函数。`contentChild` 和 `contentChildren` 函数返回一个信号;因此,`ngAfterContentInit` 生命周期方法中的代码也被修改了。该方法在访问属性并将结果分配给变量之前调用信号函数。
将实例成员转换为计算信号
appendHeader = computed(() => `${this.header().nativeElement.textContent} Appended`); list = computed(() => this.body().map((p) => p.nativeElement.textContent).join('---'));组件移除了 `fterContentInit` 接口和 `ngAfterContentInit` 方法,将 `appendHeader` 和 `list` 转换为计算信号,HTML 模板调用函数显示 `appenderHeader` 和 `list` 的值。
export class AppComponent implements AfterViewInit { readonly queries = viewChild.required(QueriesComponent); readonly aComponents = viewChildren('a'); viewChildName = ''; numAComponents = 0; ngAfterViewInit(): void { this.viewChildName = this.queries().name; this.numAComponents = this.aComponents().length; } }该原理图将 `ViewChild` 装饰器迁移到具有正确类型的 `viewChild` 函数。同样,该原理图将 `ViewChildren` 装饰器迁移到 `viewChildren` 函数。`viewChild` 和 `viewChildren` 函数返回一个信号;因此,`ngAfterViewInit` 生命周期方法中的代码也被修改了。该方法在访问属性并将结果分配给变量之前调用信号函数。
将实例成员转换为计算信号
viewChildName = computed(() => this.queries().name); numAComponents = computed(() => this.aComponents().length);ViewChildName: {{ viewChildName() }}
numAComponents: {{ numAComponents() }}
组件移除了 `AfterViewInit` 接口和 `ngAfterViewInit` 方法,将 `viewChildName` 和 `numAComponents` 转换为计算信号,最后 HTML 模板调用函数显示值。
参考:
信号迁移:https://github.com/angular/angular/tree/main/packages/core/schematics/ng-generate/signals 装饰器分支:https://github.com/railsstudent/ithome2024-demos/tree/refactor/day40-decorators/projects/day40-migration-schematic-demo 主分支:https://github.com/railsstudent/ithome2024-demos/tree/main/projects/day40-migration-schematic-demo