Angular 2+缓存的HTTP请求强制重载



在我的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请求分配给它,并将pipeshareReplay一起分配给它,这将缓存响应并在每次订阅该可观察对象时返回它。
  • 返回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>

相关内容

  • 没有找到相关文章

最新更新