toSignal
函數的回傳類型為 Signal<T | undefined>
。 Observable
是惰性的,當事件發生時發出第一個值。因此,在 Observable 發出第一個值之前,signal
是 undefined
。 如果 toSignal
函數希望 Observable
同步發出,例如 BehaviourSubject
或 startWith
,它可以為第二個參數提供 requireSync: true
選項。
在這篇文章中,我將展示 requireSync 選項的兩個用例。
HttpClient
透過 id 查詢一個人,startWith
運算子提供一個初始值。BehaviorSubject
值的按鈕。export type Person = {
name: string;
height: string;
mass: string;
hair_color: string;
skin_color: string;
eye_color: string;
gender: string;
films: string[];
}
import { HttpClient } from "@angular/common/http";
import { inject, Injectable } from "@angular/core";
import { catchError, Observable, of, startWith } from "rxjs";
import { Person } from "./person.type";
const URL = 'https://swapi.dev/api/people';
const DEFAULT: Person = {
name: '',
height: '',
mass: '',
hair_color: '',
skin_color: '',
eye_color: '',
gender: '',
films: [],
};
@Injectable({
providedIn: 'root'
})
export class StarWarService {
private readonly http = inject(HttpClient);
getData(id: number): Observable<Person> {
return this.http.get<Person>(`${URL}/${id}`).pipe(
startWith(DEFAULT)
catchError((err) => {
console.error(err);
return of(DEFAULT);
}));
}
}
StarWarService
建立一個帶有 getData
方法的來呼叫 StarWar API 來檢索人員。 HttpClient
將結果傳送給傳回初始值的 startWith
運算子。 因此,此方法的傳回類型為 Observable<Person>
。
import { ChangeDetectionStrategy, Component, inject, Injector, input, OnChanges, Signal } from '@angular/core';
import { StarWarService } from './star-war.service';
import { toSignal } from '@angular/core/rxjs-interop';
import { StarWarPersonComponent } from './star-war-person.component';
import { Person } from './person.type';
@Component({
selector: 'app-star-war',
standalone: true,
imports: [NgStyle, StarWarPersonComponent],
template: `
<h3>Star War Jedi vs Sith</h3>
<app-star-war-person [person]="light()" />
<app-star-war-person [person]="evil()" />
</div>
`,
})
export class StarWarComponent implements OnChanges {
// required signal input
jedi = input.required<number>();
// required signal input
sith = input.required<number>();
starWarService = inject(StarWarService);
injector = inject(Injector);
light!: Signal<Person>;
evil!: Signal<Person>;
ngOnChanges(): void {}
}
在 StarWarComponent
元件中,我注入 StarWarService
和元件的 injector。此外,我聲明了 light
和 evil
的 signals 來儲存從 toSignal
函數傳回的結果。觀察到 signal
刪除了類型中的 undefined
。
interface ToSignalOptions<T> {
initialValue?: unknown;
requireSync?: boolean;
injector?: Injector;
manualCleanup?: boolean;
rejectErrors?: boolean;
equal?: ValueEqualityFn<T>;
}
ToSignalOptions
選項有一個 requireSync
屬性,我用它來確保 Observables
在訂閱時發出值。
export class StarWarComponent implements OnChanges {
… same as before …
ngOnChanges(): void {
this.light = toSignal(this.starWarService.getData(this.jedi()), {
injector: this.injector,
requireSync: true,
});
this.evil = toSignal(this.starWarService.getData(this.sith()), {
injector: this.injector,
requireSync: true
});
}
}
在 ngOnChanges
方法中,我呼叫 service 來取得 Observables,並使用 toSignal
函數建立 signal。第二個參數是元件的 injector 和 requireSync
的選項。
<app-star-war-person [person]="light()" kind="Jedi Fighter" />
<app-star-war-person [person]="evil()" kind="Sith Lord" />
接下來,我將 light
和 evil
signals 傳遞給 StarWarPersonComponent
元件,以顯示絕地武士和西斯領主的詳細資訊。
import { Route } from '@angular/router';
export const routes: Route[] = [
{
path: 'requireSync-example',
loadComponent: () => import('./require-sync/example.component'),
data: {
btnValues: [-5, -3, 1, 2, 4]
}
},
];
在 routes
array中, requireSync-example
路由的路由資料是一個數字數組。
export const appConfig = {
providers: [
provideRouter(routes, withComponentInputBinding()),
]
}
在 appConfig
中,provideRouter
函數的 withComponentInputBinding
功能將路由資料綁定到ExampleComponent
元件的 required signal input。
@Component({
selector: 'app-requireSync-example',
standalone: true,
template: `
<div>
@for (v of btnValues(); track v) {
<button (click)="update(v)">{{ v }}</button>
}
</div>
<div>
<p>total: {{ total() }}</p>
<p>source: {{ source.getValue() }}</p>
<p>sum: {{ sum() }}</p>
</div>
<button (click)="changeArray()">Update the BehaviorSubject</button>
`,
})
export default class ExampleComponent {
btnValues = input.required<number[]>();
something = new BehaviorSubject(0);
total = toSignal(this.something, { requireSync: true });
source = new BehaviorSubject([1,2,3,4,5]);
sum = toSignal(
this.source.pipe(map((values) => values.reduce((acc, v) => acc + v, 0))), { requireSync: true });
update(v: number) {
this.something.next(this.something.getValue() + v);
}
changeArray() {
const values = this.source.getValue().length <= 5 ? [11,12,13,14,15,16,17,18] : [1,2,3,4,5];
this.source.next(values);
}
}
Something
是初始值為 0 的 BehaviorSubject
,toSignal
函數會從中建立一個 signal。 requireSync
選項是可能的,因為 BehaviorSubject
在被訂閱時可以立即發出一個值。點選時,按鈕會呼叫 update
方法來更新 BehaviorSubject
。 HTML 範本在收到新值時顯示 total
的值。
Source
是另一個儲存數字 array 的 BehaviorSubject
。 然後,BehaviorSubject
傳送給 map
運算子來計算總和。 toSignal
函數和 requireSync: true
斷言事件流 (event stream) 在訂閱時會發出總和。 點選按鈕會執行 changeArray
方法來變更 source
BehaviorSubject。由於 total
signal 訂閱事件流 ,因此範本呈現來 source
和 total
的新值。
Observable
是 BehaviorSubject
或由諸如 startWith
或 of
之類的的 RxJS 運算子產生值,我們可以將 requireSync
選項傳遞給 toSignal
函數。toSignal
有 requireSync: true
但 Observable 沒有立即發出值,則會拋出錯誤。鐵人賽的第 34 天到此結束。