昨天,我介紹了toSignal
函數,它可以將可觀察序列(Observable)轉換成訊號 (Signal)。事實上,我們也可以使用 toObservable 函數將訊號轉換成可觀察序列。
以下是toObservable
的一些使用案例:
以下示例演示了如何基於 HTML 輸入欄位中的 ID 獲取星球大戰角色的資訊。首先,我們使用toObservable
將id
signal轉換成可Observable
,然後再使用各種RxJS運算子來獲取角色數據。接著,我們使用 forkJoin
函數來獲取角色所出演的電影列表。
export type Person = {
name: string;
height: string;
mass: string;
hair_color: string;
skin_color: string;
eye_color: string;
gender: string;
films: string[];
}
// 建立人物類型 (Create a Person type)
export function getPerson(id: number, injector: Injector) {
return runInInjectionContext(injector, () => {
const http = inject(HttpClient);
const URL = 'https://swapi.dev/api/people';
return http.get<Person>(`${URL}/${id}`).pipe(
catchError((err) => {
console.error(err);
return of(undefined);
}));
});
}
定義getPerson
函數以根據id
檢索 Star War 角色。
export function getFilmTitle(url: string, injector: Injector): Observable<string> {
return runInInjectionContext(injector, () => {
const http = inject(HttpClient);
return http.get<{ title: string }>(url)
.pipe(
map(({ title }) => title),
catchError((err) => {
console.error(err);
return of('');
})
);
});
}
定義一個getFilmTitle
函數,該函數接受電影URL並呼叫Star War API來檢索電影標題。 然後,我將在元件中匯入這兩個函數來檢索要顯示的角色和電影標題。
export class App {
id = signal(1);
injector = inject(Injector);
person$ = toObservable(this.id)
.pipe(
debounceTime(300),
distinctUntilChanged(),
filter((v) => v >= 1 && v <= 83),
switchMap((v) => getPerson(v, this.injector)),
shareReplay(1),
);
films$ = this.person$.pipe(
map((p) => {
const films = p ? p.films : [];
return films.map((url) => getFilmTitle(url, this.injector));
}),
concatMap((x) => forkJoin(x)),
);
}
toObservable
將id
signal轉換為 Observable 並將值傳送到
id
與當前id
不同時繼續id
是否在 1 到 83 之間Observable
person$
Observablefilm$
Observable 從角色中擷取電影 URL,將取得電影標題的Observables
傳遞給forkJoin
來取得數值,並使用concatMap
展平內部Observables
。
<div>
<label for="id">
<span>Id: </span>
<input id="id" name="id" type="number" min="1” [(ngModel)]="id">
</label>
</div>
<div>
@if(person$ | async; as person) {
<p>Name: {{ person.name }}</p>
<p>Height: {{ person.height }}</p>
<p>Mass: {{ person.mass }}</p>
<p>Hair Color: {{ person.hair_color }}</p>
<p>Skin Color: {{ person.skin_color }}</p>
<p>Eye Color: {{ person.eye_color }}</p>
<p>Gender: {{ person.gender }}</p>
} @else {
<p>No info</p>
}
@if (films$ | async; as films) {
<p>Movies</p>
@for(film of films; track film) {
<ul style="padding-left: 1rem;">
<li>{{ film }}</li>
</ul>
}
} @else {
<p>No movie</p>
}
</div>
此範本使用async pipe解析Observables
並顯示結果。
function buildTimerString(currentSeconds: number) {
const secondsInHour = 3600;
const secondsInMinute = 60;
const hours = Math.floor(currentSeconds / secondsInHour);
const minutes = Math.floor((currentSeconds - hours * secondsInHour) / secondsInMinute);
const seconds = currentSeconds - hours * secondsInHour - minutes * secondsInMinute;
const padHours = hours < 10 ? `0${hours}` : `${hours}`;
const padMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
const padSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`;
return `${padHours}:${padMinutes}:${padSeconds}`;
}
buildTimerString
是一個實用程式函數,它接受整數並建立<hours>:<months>:<seconds>
格式的字串。 例如,10秒錶示為"00:00:10",4000 秒錶示為"01:06:40"。
export class App {
amount = signal(60);
unit = signal('1')
totalSeconds = computed(() => this.amount() * parseInt(this.unit(), 10));
timer$ = toObservable(this.totalSeconds).pipe(
debounceTime(300),
switchMap((x) => timer(0, 1000).pipe(
map((y) => x - y),
take(x + 1),
)),
shareReplay(1),
);
timerString$ = this.timer$.pipe(map((x) => buildTimerString(x)));
}
類似地,toObservable
將totalSeconds
signal轉換為Observable
並將值傳送到switchMap
取消先前的計時器並建構一個從x + 1
開始一直到0
的新計時器。shareReplay
快取最後的結果。
timerString$
將總秒數對應到計時器字串,並將其顯示在範本中。
<div>
<div>
<label for="id">
<span>Id: </span>
<input id="id" name="id" type="number" min="1" [(ngModel)]="amount">
</label>
<label for="unit">
<span>Unit: </span>
<select [(ngModel)]="unit">
<option value="1">seconds</option>
<option value="60">minutes</option>
<option value="3600">hours</option>
</select>
</label>
</div>
</div>
<p>{{ timerString$ | async }}</p>
amount
和unit
signal double binded到ngModel
,以便對HTML控制項的任何變更都會寫回它們。
當任何signal更新時,Angular都會重新計算等於總秒數的totalSeconds
signal。timer$
和timerString$
Observables追蹤totalSeconds
signal,並在範本中顯示新的計時器字串。
toObservable
可以將signal
轉換為Observable
。toSignal
類似,toObservable
必須出現在injection context
中,例如constructor
, field initializations
或factory function
。當toObservable
在injection context
之外調用時,必須提供injector
。toObservable
可以使用pipe
向其他RxJS運算子(operators)發出值,以產生結果。鐵人挑戰賽的第6天就這樣結束了。
Stackblitz Demos:
可改寫成
如此就可替換成
unit = signal('1') => unit=signal(1) ,後續計算也不用特地 parseInt
option [ngValue]="1">seconds
option [ngValue]="60">minutes
option [ngValue]="3600">hours
(被吃字,補上)
Thank you. I don't know ngValue.
謝謝。 我不知道 ngValue。