input()、output()、viewChild()、viewChildren()、contentChild()、contentChildren()、outputFromObservable()和outputToObservable()在Angular 19中處於穩定狀態。 應用程式可以開始使用 Angular 原理圖從裝飾器轉換為訊號。
Angular 19 將訊號輸入 (signal input)、輸出 (output) 和查詢 (queries) 遷移合併為單一訊號遷移 (signal migration)。開發人員可以打開終端,執行 schematic,然後從文字選單中選擇一個或所有遷移。
讓我們在這篇文章中逐步進行遷移。
您可以在此 feature branch 中找到 decorators 版本:
https://github.com/railsstudent/ithome2024-demos/tree/refactor/day40-decorators/projects/day40-migration-schematic-demo
@Component({
selector: 'app-some',
standalone: true,
imports: [FormsModule],
template: `
<div>
<p>bgColor: {{ bgColor }}</p>
<p>name: {{ name }}</p>
</div>
`,
})
export class SomeComponent {
@Input({ required: true, alias: 'backgroundColor'}) bgColor!: string;
@Input({ transform: (x: string) => x.toLocaleUpperCase() }) name: string = 'input decorator';
}
SomeComponent
元件使用 Input
裝飾器接收來自父元件的輸入。 bgColor
是必要的輸入,具有別名backgroundColor
。 name
是將傳入值轉換為大寫的輸入。
export class SomeComponent {
@Output() triple = new EventEmitter<number>();
@Output('cube') powerXBy3 = new EventEmitter<number>();
numSub = new BehaviorSubject<number>(2);
@Output() double = this.numSub.pipe(map((n) => n * 2));
}
同一組件還具有三個輸出裝飾器 (Output decorator)。 triple
是一個發出數字的事件發射器。 powerXBy3
也是一個帶有別名 cube
的數字事件發射器。 double
使用 numSub
BehaviorSubject 的值,乘以二,並將結果傳送給父元件。
在終端機中運行 Angular 訊號 schematic 以遷移程式碼。
ng g @angular/core:signals
在選擇選單中,選擇所有遷移並繼續下一步。
訊號輸入遷移後,
readonly bgColor = input.required<string>({ alias: "backgroundColor" });
readonly name = input<string, string>('input decorator',
{ transform: (x: string) => x.toLocaleUpperCase() });
對於 bgColor
,使用 alias
選項呼叫 input.required
函數。 對於 name
,使用初始值和 transform
選項來呼叫輸入以將輸入文字大寫。 遷移對父元件沒有影響。
@Component({
selector: 'app-some',
standalone: true,
imports: [FormsModule],
template: `
<div>
<p>bgColor: {{ bgColor() }}</p>
<p>name: {{ name() }}</p>
`,
})
export class SomeComponent {}
bgColor
和 name
是訊號輸入; HTML 模板呼叫訊號函數來顯示它們的值。
輸出遷移後,
readonly triple = output<number>();
readonly powerXBy3 = output<number>({ alias: 'cube' });
numSub = new BehaviorSubject<number>(2);
double = outputFromObservable(this.numSub.pipe(map((n) => n * 2)));
triple
是一個輸出函數,產出數字。 powerX3
是一個輸出函數,其第一個參數為 alias
選項。別名的值為 cube
,與之前相同。 double
使用 outputFromObservable
函數將 Observable 轉換為 output。 遷移對父元件也沒有影響。
num = signal(2);
double = output<number>();
updateNum(value: number) {
this.num.set(value);
this.double.emit(value * 2);
}
numSub
BehaviorSubject 轉換為 num
訊號,double
OutputRef 轉換為輸出函數。我定義了一個 updateNum
方法來覆寫 num
訊號並將該值傳送到 double
自訂事件。
<div>
Num: <input type="number" [ngModel]="n" (ngModelChange)="updateNum($event)" />
</div>
新值不會傳遞給 numSub
Subject,而是傳遞給 updateNum
方法。
export class QueriesComponent implements AfterContentInit {
@ContentChild('header') header!: ElementRef<HTMLDivElement>;
@ContentChildren('p') body!: QueryList<ElementRef<HTMLParagraphElement>>;
appendHeader = '';
list = '';
ngAfterContentInit(): void {
this.appendHeader = `${this.header.nativeElement.textContent} Appended`;
this.list = this.body.map((p) => p.nativeElement.textContent).join('---');
}
}
QueriesComponent
元件會套用 ContentChild
和 ContentChildren
裝飾器 (decorators) 來查詢 HTML 元素,進而投影到 <ng-content>
元素的。
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<ElementRef<HTMLDivElement>>('header');
readonly body = contentChildren<ElementRef<HTMLParagraphElement>>('p');
appendHeader = '';
list = '';
ngAfterContentInit(): void {
this.appendHeader = `${this.header().nativeElement.textContent} Appended`;
this.list = this.body().map((p) => p.nativeElement.textContent).join('---');
}
}
<div>Appendheader: {{ appendHeader() }}</div>
<div>List: {{ list() }}</div>
此原理圖將 ContentChild
裝飾器遷移到具有正確類型的 contentChild
函數。 同樣,原理圖將 ContentChildren
裝飾器遷移到 contentChildren
函數。contentChild
和 contentChildren
函數傳回一個訊號;因此,ngAfterContentInit
生命週期方法中的程式碼也被修改。
appendHeader = computed(() => `${this.header().nativeElement.textContent} Appended`);
list = computed(() => this.body().map((p) => p.nativeElement.textContent).join('---'));
此元件刪除了 AfterContentInit
介面和 ngAfterContentInit
方法。 appendHeader
和 list
被轉換為計算訊號 (computed signals)。 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);
<p>ViewChildName: {{ viewChildName() }}</p>
<p>numAComponents: {{ numAComponents() }}</p>
該元件刪除了 AfterViewInit
介面和 ngAfterViewInit
方法。 viewChildName
和 numAComponents
被轉換為計算訊號 (computed signals)。 最後,HTML 模板呼叫函數來顯示 viewChildName
和 numAComponents
的值。。