可观察服务上不需要的订阅



按照缓存的服务模式,我使用通过在各个组件中使用从它创建的只读Observable订阅的BehaviourSubject

// credential.service.ts
private _credentialList$: BehaviorSubject<Credential[]>
readonly credentialListObv$: Observable<Credential[]>
...
this. credentialListObv$ = this.credentialList$.asObservable()
...
getCredentialList(...): Observable<Credential[]> {
return this.http.get<Credential[]>(
``
).pipe(
tap(credList => {
...
this._credentialList$.next(credList)
}),
)
}

因此,从组件中,我触发getCredentialList方法以发出_credentialList$主题获取的列表,因此所有credentialListObv$的订阅者都应该获得该值。

问题是,当我创建我的组件时,当我调用getCredentialList方法时,我将得到不必要的订阅

// credential-list.component.ts
private _credentials$: Observable<Credential[]>
constructor(
private credentialService: CredentialService,
) {
this._credentials$ = this.credentialService.credentialListObv$.pipe(
tap(() => {console.log("sub")}),
...
)
}
ngAfterViewInit(): void {
this.credentialService.getCredentialList(...).subscribe()
}
get credentials$() {
return this._credentials$
}
// credential-list.component.html
<mat-expansion-panel *ngFor="let credential of credentials$ | async; let i=index">
...

结果是,当我访问页面"sub"将被打印两次。第一个订阅是从组件模板内的async管道发生的,但是如果我永远不会从credential-list组件调用this._credentials$上的subscribe,为什么还有另一个订阅?

因为你使用了BehaviourSubject,第一次发射是它的初始值。然后,在ngafterviewit中,您订阅getCredentialList,它将新值推送到相同的behavi_subject.

尝试记录值,而不是键入字符串:

tap((val) => {console.log(val)}),

第一个应该是您在实例化主题(this._credentialList$ = new BehaviorSubject(...)时使用的默认值

这是因为你还订阅了返回HTTP可观察对象的service方法。所以现在你的组件对一个http调用有两个活动订阅。

Angular中的HTTP方法在发出一个值后就会完成,所以你可以安全地在方法中订阅可观察对象。

getCredentialList(...): Observable<Credential[]> {
this.http.get<Credential[]>(
``
).pipe(
tap(credList => {
...
this._credentialList$.next(credList)
}),
).subscribe();
}

然后在你的组件中,保持你的可观察对象不变,但不要订阅该方法。

ngAfterViewInit(): void {
this.credentialService.getCredentialList(...);
}

现在调用服务方法触发HTTP请求,您的私有_credentials$可观察对象仍然从BehaviorSubject获取数据。


编辑:既然你说你不想在服务中订阅任何东西,我可以为你提供一个解决方案,只需要在组件模板中订阅一次。

// credential.service.ts
private credentialParams$ = new Subject<ApiParameters>();
public credentialListObv$: Observable<Credential[]>;
...
this.credentialListObv$ = this.credentialParams$
.pipe(
switchMap(urlParameters=>
this.http.get<Credential[]>(`url with ${urlParameters}`)
)
);
...
getCredentialList(newParams:ApiParameters): void {
this.credentialParams$.next(newParams);
}

使用主题发出API调用所需的任何参数。然后,可观察对象接受发出的参数,并直接返回HTTP请求的可观察对象。

// credential-list.component.ts
public credentials$: Observable<Credential[]>
constructor(
private credentialService: CredentialService,
) {
this.credentials$ = this.credentialService.credentialListObv$.pipe(
tap(() => {console.log("sub")}),
...
);
}
ngAfterViewInit(): void {
this.credentialService.getCredentialList(apiParams);
}
// credential-list.component.html
<mat-expansion-panel *ngFor="let credential of credentials$ | async; let i=index">

在组件中,我们只订阅服务可观察对象。在init生命周期中,我们调用服务方法。这会导致你的组件可观察对象对从服务可观察对象返回的HTTP请求做出反应。

注:如果你知道可观察对象完成了(比如HTTP请求),那么在服务中订阅某些东西是可以的。

最新更新