首页 > 解决方案 > 在 Angular 服务中按 id 缓存 RxJS 主题映射中的状态

问题描述

我为每个ownerId. 我正在尝试通过使用Map<string, ReplaySubject<Widget[]>>.

@Injectable({
    providedIn: 'root',
})
export class WidgetService {

    private readonly widgetsByOwnerId: Map<string, ReplaySubject<Widget[]>> =
        new Map<string, ReplaySubject<Widget[]>>();

    constructor(
        private readonly httpClient: HttpClient
    ) {}

    getWidgets(ownerId: string): Observable<Widget[]> {
        if (!this.widgetsByOwnerId.has(ownerId)) {
            const widgets$ = this.httpClient.get<Widget[]>(`api/${ownerId}/widgets`);
            const widgetsCache$ = new ReplaySubject<Widget[]>(1);
            widgets$.subscribe(widgetsCache$);
            this.widgetsById.set(ownerId, widgetsCache$);
        }
        return this.widgetsById.get(ownerId).asObservable();
    }

    createWidget(widget: Widget): Observable<Widget> {
        return this.httpClient
            .post<Widget>(`api/${widget.ownerId}/widgets`, widget)
            .pipe(
                tap((createdWidget): void => {
                    this.widgetsByOwnerId.forEach((widgets$, ownerId) =>
                        widgets$.pipe(take(1)).subscribe({
                            next: (widgets: Widget[]) => {
                                if(createdWidget.ownerId == ownerId || isOwnedById) {
                                    widgets.push(createdWidget);
                                    widgets$.next(widgets);
                                }
                            },
                        })
                    );
                }
            );
    }

    //additional CRUD functions excluded for brevity
}

一些特殊类型的widget可以被多个widget所有者拥有,所以一些CRUD函数WidgetService可以next()放在multipleownerIdReplaySubjects上。在上面的示例中,为了简洁起见,我省略了一些逻辑,使用isOwnedById.

该服务的消费者如下所示:

@Component({
    selector: 'app-widgets',
    templateUrl: './widgets.component.html',
})
export class WidgetsComponent implements OnInit, OnDestroy {

    widgets: Widget[];

    private readonly ngUnsubscribe$: Subject<unknown> = new Subject();

    constructor(
        private readonly widgetService: WidgetService
    ) {}

    ngOnInit(): void {
        const ownerId = this.getOwnerId();
        this.getWidgets(ownerId);
    }

    ngOnDestroy(): void {
        this.ngUnsubscribe$.next();
        this.ngUnsubscribe$.complete();
    }

    private getOwnerId(): string {
        ...
    }

    private getWidgets(ownerId: string): void {
        this.widgetService
            .getWidgets(ownerId)
            .pipe(takeUntil(this.ngUnsubscribe$))
            .subscribe({
                // TODO: this next is called once when first subscribed, but not when future widgets are created
                next: (widgets) => {
                    this.widgets = widgets;
                },
            });
    }

}

由于某种原因,当消费组件第一次初始化时,它Widget[]成功地获得了缓存。但是,当像在其他组件中执行的 CRUD 操作时WidgetService.createWidget()(当实例WidgetComponent保持活动状态时,例如 before WidgetComponent.ngOnDestroy()),此WidgetComponent使用者永远不会收到next()-ed 更新(如注释中所述TODO)。

关于如何解决这个问题的任何建议,以便消费者继续从WidgetServices获取更新ReplaySubject?谢谢。

这个问题与这个问题有点相似,但差异很大,以至于我无法弄清楚如何使他们的答案适应这个用例。

标签: angularrxjs

解决方案


我认为这里的主要问题是:

 const widgets$ = this.httpClient.get<Widget[]>(`api/${ownerId}/widgets`);
 const widgetsCache$ = new ReplaySubject<Widget[]>(1);
 widgets$.subscribe(widgetsCache$);

httpClient创建一个 finity Observable 意味着一旦请求完成或失败,它将完成。

结果,您ReplaySubject传递的 as Observertosubscribe方法将完成,您将不再收到任何事件。

要修复它,您可以将subscribe实现替换为以下内容:

widgets$.subscribe(res => widgetsCache$.next(res));

推荐阅读