【问题标题】:Unwanted subscription on observable service不需要的可观察服务订阅
【发布时间】:2021-11-04 12:11:31
【问题描述】:

按照缓存服务模式,我使用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 组件调用 subscribe 上的 subscribe,为什么还有另一个订阅呢?

【问题讨论】:

    标签: angular typescript rxjs angular-services behaviorsubject


    【解决方案1】:

    那是因为您还订阅了返回 HTTP observable 的服务方法。所以现在你的组件有两个对一个 http 调用的有效订阅。

    Angular 中的 HTTP 方法在发出一个值后完成,因此您可以安全地订阅方法内的 observable。

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

    然后在你的组件中,让你的 observable 保持不变,但不要订阅该方法。

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

    现在调用 service 方法会触发 HTTP 请求,而你的私有 _credentials$ observable 仍然会从 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 接受发出的参数并直接返回 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 生命周期中,我们调用 service 方法。这会导致您的组件 observable 对将从服务 observable 返回的 HTTP 请求做出反应。

    附:如果您知道 observable 完成(如 HTTP 请求),则可以订阅服务中的某些内容。

    【讨论】:

    • 我无法在服务内订阅
    • 为你更新了我的答案。
    • 反正我订阅了一次,服务方法返回的observable的订阅不处理tap log
    • 模板中的异步管道也触发了订阅。
    • 是的,但这是我对 _credentials$ (credentialListObv$) 的唯一订阅
    【解决方案2】:

    因为你使用BehaviourSubject,所以第一个发射是它的初始值。然后,稍后,在 NgAfterViewInit 中,您订阅 getCredentialList,它将新值推送到相同的 BehaviourSubject

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

    tap((val) =&gt; {console.log(val)}),

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

    【讨论】:

    • 你是对的,但为什么我会同时获得默认值和获取的值?我只通过组件模板订阅_credentials$一次
    • 所以订阅只有一个,发出的值是两个,对吗?
    • 因为当credentials$ | async 订阅时它会发出 BehaviourSubject 的默认值,然后当来自ngAfterViewInit 的子脚本发出时它会将新值推送到您的主题,所以credentials$ 再次发出
    猜你喜欢
    • 2011-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-21
    • 1970-01-01
    • 1970-01-01
    • 2019-06-19
    相关资源
    最近更新 更多