在我的angular服务中,我有一个缓存的http请求,当我的组件初始化时,它会被调用一次
//variables
private readonly URL: string = 'http://makeup-api.herokuapp.com/api/v1/products.json';
private cachingSubject: ReplaySubject<IProduct[]>
private _products: IProduct[] = [];
//method
public getAllProducts(): Observable<IProduct[]> {
if (!this.cachingSubject) {
this.cachingSubject = new ReplaySubject<IProduct[]>(1);
this.http.get<IProduct[]>(this.URL)
.subscribe((productsData: IProduct[]) => {
this._products = productsData;
this.cachingSubject.next(productsData);
});
}
return this.cachingSubject.asObservable();
}
和我的组件创建服务方法时:
public products: IProduct[] = [];
private destroySubject$: Subject<void> = new Subject<void>();
ngOnInit(): void {
this.productService
.getAllProducts()
.pipe(takeUntil(this.destroySubject$))
.subscribe((products: IProduct[]) => {
this.products = products;
})
}
public onForceReload(): void {
//need to reload onClick
}
问题是,我如何从我的缓存请求强制重新加载(重新缓存)?重新创建getAllProducts
我认为实现您正在寻找的最简单的方法是使用单个Subject作为触发器来获取数据。然后在服务上定义一个allProducts$
可观察对象(而不是方法),它依赖于这个"fetch subject"
。提供一个简单的refreshProducts()
方法,在获取主体上调用.next()
,这将导致allProduct$
重新获取数据。
消费者可以简单地订阅allProducts$
并接收最新的产品数组。他们可以调用refreshProducts()
来重新加载数据。不需要重新分配对象或可观察对象的引用。
export class ProductService {
private fetch$ = new BehaviorSubject<void>(undefined);
public allProducts$: Observable<IProduct[]> = this.fetch$.pipe(
exhaustMap(() => this.http.get<IProduct[]>('url')),
shareReplay(),
);
public refreshProducts() {
this.fetch$.next();
}
}
这是一个工作的StackBlitz演示。
以下是allProducts$
的流程:
- 以
fetch$
开头,意味着只要fetch$
发出 ,它就会执行。 exhaustMap
将订阅一个"内部可观察对象"并排放它的排放物。在这种情况下,内部可观察对象是http调用。shareReplay
用于向新订阅者发出先前的发射,因此直到refreshProducts()
被调用时才进行http调用(如果您一次只有一个订阅者,则不需要此操作,但通常在服务中,使用它是一个好主意)
我们将fetch$
定义为BehaviorSubject
的原因是allProducts$
在fetch$
发出之前不会发出。BehaviorSubject
最初会发出一个默认值,所以它会导致我们的allProducts$
在用户不需要点击重新加载按钮的情况下执行。
请注意,服务中没有发生订阅。这是一件好事,因为它允许我们的数据是惰性的,这意味着我们不会仅仅因为某个组件注入了服务而获取数据,我们只会在有订阅者的情况下获取数据。
同样,这意味着我们的服务有一个单向的数据流,这使得事情更容易调试。消费者只能通过订阅公共可观察对象来获取数据,并通过调用服务上的方法来修改数据……但是这些方法不返回数据,只导致数据通过可观察对象推送。关于这个话题,有一个非常好的视频,主角是Thomas Burleson (前Angular团队成员)。
你看,我用allProducts
这个名字代替了getAllProducts
。由于allProducts$
是一个可观察对象,订阅的行为意味着"获取"。
我喜欢把可观察对象想象成总是推送最新值的小数据源。当你订阅时,你在倾听未来的价值,但消费者并没有"得到"。他们。
我知道这太多了,但我还有一个小小的建议,我认为这能让代码更清晰。
这是AsyncPipe
的用法。
所以你的组件代码可以简化为:
export class AppComponent {
public products$: Observable<IProduct[]> = this.productService.allProducts$;
constructor(private productService: ProductService) { }
public onForceReload(): void {
this.productService.refreshProducts();
}
}
和你的模板:
<ul>
<li *ngFor="let product of products$ | async">{{ product }}</li>
</ul>
注意,这里不需要在模板中订阅this.products = products;
。异步管道为您订阅,更重要的是为您取消订阅。这意味着,你不再需要摧毁对象了!
这是之前的StackBlitz分支,更新为使用async管道。
您可以这样做:
- 创建一个可观察对象
cachedRequest$
,将HTTP请求分配给它,并将pipe
与shareReplay
一起分配给它,这将缓存响应并在每次订阅该可观察对象时返回它。 - 返回
cachedRequest$
如果有值,或分配HTTP请求(与shareReplay
)如果没有。 - 将
forceReload
参数传递给你的函数以强制重新加载,如果它为真,只需将cachedRequest$
设置为null,重新初始化。
试如下:
cachedRequest$: Observable<IProduct[]>;
public getAllProducts(forceReload = false): Observable<IProduct[]> {
if (forceReload) this.cachedRequest$ = null;
if (!this.cachedRequest$) {
this.cachedRequest$ = this.http.get<IProduct[]>(this.URL).pipe(
tap(productsData => (this._products = productsData)),
shareReplay(1)
);
}
return this.cachedRequest$;
}
那么在你的组件中,最好用另一种方式来处理请求,以避免对源可观察对象进行多重订阅,所以你可以这样做:
products$: Observable<IProduct[]>;
ngOnInit(): void {
this.loadProducts();
}
public onForceReload(): void {
//need to reload onClick
this.loadProducts(true);
}
private loadProducts(forceReload = false) {
this.products$ = this.productService.getAllProducts(forceReload);
}
然后在你的组件模板中像这样使用它:
<ng-container *ngIf="products$ | async as products">
<!-- YOUR CONTENT HERE -->
</ng-container>