不需要的可观察服务订阅
Unwanted subscription on observable service
按照缓存服务模式,我正在使用 BehaviourSubject
通过使用在各种组件中创建的只读 Observable
订阅
// 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
,为什么还有另一个订阅?
那是因为您还订阅了 returns HTTP 可观察的服务方法。所以现在您的组件对一个 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 调用所需的任何参数。然后 observable 接受发出的参数并直接 returns HTTP 请求 observable。
// 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 请求做出反应。
P.S。如果您知道 observable 已完成(如 HTTP 请求),则可以订阅服务中的某些内容。
因为你使用了BehaviourSubject,第一次发射是它的初始值。然后,稍后,在 NgAfterViewInit 中,您订阅 getCredentialList
,它将新值推送到相同的 BehaviourSubject.
尝试记录值,而不是键入字符串:
tap((val) => {console.log(val)}),
第一个应该是您在实例化主题时使用的默认值(this._credentialList$ = new BehaviorSubject(...
)
按照缓存服务模式,我正在使用 BehaviourSubject
通过使用在各种组件中创建的只读 Observable
订阅
// 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
,为什么还有另一个订阅?
那是因为您还订阅了 returns HTTP 可观察的服务方法。所以现在您的组件对一个 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 调用所需的任何参数。然后 observable 接受发出的参数并直接 returns HTTP 请求 observable。
// 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 请求做出反应。
P.S。如果您知道 observable 已完成(如 HTTP 请求),则可以订阅服务中的某些内容。
因为你使用了BehaviourSubject,第一次发射是它的初始值。然后,稍后,在 NgAfterViewInit 中,您订阅 getCredentialList
,它将新值推送到相同的 BehaviourSubject.
尝试记录值,而不是键入字符串:
tap((val) => {console.log(val)}),
第一个应该是您在实例化主题时使用的默认值(this._credentialList$ = new BehaviorSubject(...
)