我需要一个基于信号的实用程序来跟踪使用可观察对象执行的多个异步操作的状态(例如 HTTP 调用)。因此,我也可以使用
OnPush
更改检测策略在组件中使用这些信号。
假设我有一个
AnimalService
,你用它来取动物:
@Injectable({
providedIn: 'root',
})
export class AnimalService {
private readonly httpClient = inject(HttpClient);
fetchCats() {
return this.httpClient.get('animals.org/cats');
}
fetchDogs() {
return this.httpClient.get('animals.org/dogs');
}
fetchChickens() {
return this.httpClient.get('animals.org/chickens')
}
}
我想要一个集中的地方来跟踪一些动物何时被装载。 另外,我想从可能不知道实际调用执行位置的地方跟踪这一点。 例如,假设组件
A
触发了 fetch 调用,但组件 B
实际上想要知道是否有动物正在加载以显示旋转器。
我通过创建一个通用的
LoadingStatusManager
实用程序解决了这个问题,如下所示:
import { computed, Signal, signal, WritableSignal } from '@angular/core';
import { catchError, Observable, tap } from 'rxjs';
export class LoadingStatusManager<KeyType extends string> {
private readonly statusMap = {} as { [key in KeyType]: WritableSignal<boolean> };
constructor(private readonly keys: KeyType[]) {
for (const key of keys) {
this.statusMap[key] = signal(false);
}
}
executeAndUpdateStatus<T>(key: KeyType, observable: Observable<T>): Observable<T> {
this.statusMap[key].set(true);
return observable.pipe(
tap(() => this.statusMap[key].set(false)),
catchError((error) => {
this.statusMap[key].set(false);
throw error;
}),
);
}
getSignalWatchingOnProps(keys?: KeyType[]): Signal<boolean> {
const keySet = keys ?? this.keys;
return computed(() => keySet.some((key) => this.statusMap[key]()));
}
}
要使用它,首先,您定义一个类型,为您要跟踪的每个调用定义一个键(字符串):
type AnimalLoadingStatusKeyType = 'fetchCats' | 'fetchDogs' | 'fetchChickens';
然后使用此类型创建一个新的
LoadingStatusManager
对象:
private readonly loadingStatusManager = new LoadingStatusManager<AnimalLoadingStatusKeyType>([
'fetchCats',
'fetchDogs',
'fetchChickens',
]);
现在使用
loadingStatusManager
对象来观察您的异步操作:
fetchCats() {
return this.loadingStatusManager.executeAndUpdateStatus(
'fetchCats',
this.httpClient.get('animals.org/cats'),
);
}
fetchDogs() {
return this.loadingStatusManager.executeAndUpdateStatus(
'fetchDogs',
this.httpClient.get('animals.org/dogs'),
);
}
fetchChickens() {
return this.loadingStatusManager.executeAndUpdateStatus(
'fetchChickens',
this.httpClient.get('animals.org/chickens'),
);
}
现在您可以轻松获得
Signal
来监视是否有任何异步操作正在进行中:
readonly areAnimalsBeingLoaded: Signal<boolean> = this.loadingStatusManager.getSignalWatchingOnProps();
这是因为我们没有将任何键传递给
getSignalWatchingOnProps
,所以默认情况下它会监视所有键。
如果我们想获得仅在一组特定按键上监视的信号,我们可以使用:
readonly areMammalsBeingLoaded: Signal<boolean> = this.loadingStatusManager.getSignalWatchingOnProps([
'fetchCats',
'fetchDogs',
]);
AnimalService
的完整代码为:
import { HttpClient } from '@angular/common/http';
import { inject, Injectable, Signal } from '@angular/core';
import { LoadingStatusManager } from './loading-status-manager';
type AnimalLoadingStatusKeyType = 'fetchCats' | 'fetchDogs' | 'fetchChickens';
@Injectable({
providedIn: 'root',
})
export class AnimalService {
private readonly httpClient = inject(HttpClient);
private readonly loadingStatusManager = new LoadingStatusManager<AnimalLoadingStatusKeyType>([
'fetchCats',
'fetchDogs',
'fetchChickens',
]);
readonly areAnimalsBeingLoaded: Signal<boolean> = this.loadingStatusManager.getSignalWatchingOnProps();
readonly areMammalsBeingLoaded: Signal<boolean> = this.loadingStatusManager.getSignalWatchingOnProps([
'fetchCats',
'fetchDogs',
]);
fetchCats() {
return this.loadingStatusManager.executeAndUpdateStatus(
'fetchCats',
this.httpClient.get('animals.org/cats'),
);
}
fetchDogs() {
return this.loadingStatusManager.executeAndUpdateStatus(
'fetchDogs',
this.httpClient.get('animals.org/dogs'),
);
}
fetchChickens() {
return this.loadingStatusManager.executeAndUpdateStatus(
'fetchChickens',
this.httpClient.get('animals.org/chickens'),
);
}
}
从外部,任何组件或服务都可以注入
AnimalService
并获取信号:
readonly animalService: inject(AnimalService);
// ...
this.animalService.areAnimalsBeingLoaded();
this.animalService.areMammalsBeingLoaded();
这也可以在使用
OnPush
更改检测策略的组件模板中使用。
感谢 Angular Signals,每次信号值发生变化时,模板都会自动刷新。