【问题标题】:Subscribtion to service observable emits even after component destruction即使在组件销毁后订阅服务 observable 也会发出
【发布时间】:2019-07-18 17:02:55
【问题描述】:

我在服务中遇到了 observable 的问题。以下代码说明了这一点:

@Injectable({
  providedIn: 'root'
})
export class MyService {
  public globalVariable: BehaviorSubject<string> = new BehaviorSubject('');
}

我有一个特征组件:

export class ComponentA implements OnInit {
   constructor(public myService : MyService ) {
      this.myService.globalVariable.next('newValue');
   }

   ngOnInit() {
      this.myService.globalVariable.subscribe(_ => console.log('=> hello'));
   }
}

应用模块:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ComponentAModule,
    ComponentBModule,
    AppRoutingModule
  ],
  providers: [MyService],
  bootstrap: [AppComponent]
})
export class AppModule {
}

最后是项目结构:

app-module.ts
app-routing.module.ts
-components
-- componentA
--- componentA.module.ts
--- componentA-routing.module.ts
--- componentA.component.ts
--- componentA.component.html
-- componentB
--- componentB.module.ts
--- componentB-routing.module.ts
--- componentB.component.ts
--- componentB.component.html

现在我面临的问题是,当我导航到componentA 时,输出是:

=> hello
=> hello

到目前为止,一切正常并且表现如我所料。第一次订阅被触发,然后componentA 的构造函数改变了globalVariable

但是,当我导航到 componentB 并导航回 componentA 时,输出是:

=> hello
=> hello
=> hello

每次我导航回componentA 时都会添加一个。好像它创建了MyService 的新实例?还是离开时不销毁订阅?

信息:没有延迟加载。

【问题讨论】:

    标签: angular service observable provider


    【解决方案1】:

    如果订阅不是由 Angular 自己处理的,则必须手动销毁它们。这基本上适用于您拥有的所有 httpClient 订阅。如果你例如使用| async 管道,Angular 负责取消订阅。

    在您的组件的ngOnDestroy() 中调用yourSubscription.unsubscribe()

    我通常做的是创建一个 BaseComponent 来为我取消订阅。通过扩展它在所有组件中使用以下类。将每个订阅调用封装在super.addSubscription()

    import { OnDestroy } from '@angular/core';
    import { Subscription } from 'rxjs';
    
    /**
     * This class handles the subscribing and unsubscribing of subscriptions to avoid memory leaks
     * and can be inherited by members
     *
     * @export
     */
    export abstract class BaseComponent implements OnDestroy {
    
    private subscriptions: Subscription[] = new Array<Subscription>();
    
    ngOnDestroy() {
        this.removeSubscriptions();
    }
    
    /**
     * Adds a subscriptions so it can be deleted in ngOnDestroy
     *
     * @param subscription The subscription that should be added
     * @memberof BaseComponent
     */
    protected addSubscription(subscription: Subscription) {
        this.subscriptions.push(subscription);
    }
    
    /**
     * Unsubscribes from any open subscriptions in the subscriptions array in ngOnDestroy
     *
     * @memberof AbstractBaseComponent
     */
    private removeSubscriptions() {
        for (let subscription of this.subscriptions) {
            subscription.unsubscribe();
        }
    }
    }
    

    更新

    假设您使用上面提供的基类,请为您的 ngOnInit() 执行以下操作:

    export class ComponentA extends BaseComponent implements OnInit {
        constructor(public myService : MyService ) {
           this.myService.globalVariable.next('newValue');
        }
        ngOnInit() {
           super.addSubscription(
               this.myService.globalVariable.subscribe(_ => console.log('=> hello'))
           )
        }
    }
    

    【讨论】:

    • 这就是我的想法,但后来我想知道为什么我不必为 httpClient 调用执行此操作。例如:this.myService.getDataX().subscribe(_ => doSomething()); ?
    • 我了解您的方法的工作方式,但这并不能解释为什么我们从不取消订阅 httpClient 调用,就像在角度教程中一样:angular.io/tutorial/toh-pt6
    • 因为它不是学习过程的一部分,并且可能会将学习者与生命周期和泄漏混淆!不过,我的回答对你有用吗?此外,本教程有时使用async 管道,正如我在回答中提到的,如果您使用async 管道,Angular 会自动处理取消订阅。
    • 是的,非常感谢。所以最后一个问题。我是否也取消订阅 httpClient 调用? (我现在知道我必须为服务中的 Observable 属性做这件事)
    • 不,不要在服务中做,而是在使用服务的组件中做!有一条规则:如果你输入 .subscribe() alwayssuper.addSubscription() 包装它(如我的回答中所述)
    【解决方案2】:

    你需要unsubscribengOnDestroy里面:

    import { Subscription } from 'rxjs';
    
    globalVariable$: Subscription;
    
    ngOnInit() {
      this.globalVariable$ = this.myService.globalVariable.subscribe(_ => console.log('=> hello'));
    }
    
    ngOnDestroy() {
      this.globalVariable$.unsubscribe();
    }
    

    【讨论】:

    • 这就是我的想法,但后来我想知道为什么我不必为 httpClient 调用执行此操作。例如:this.myService.getDataX().subscribe(_ => doSomething()); ?
    • @lolplayer101 如果你在组件或服务this.myService.getDataX().subscribe(_ =&gt; doSomething()); , you need to unsubscribe using ngOnDestroy 中订阅这样的生命钩子。销毁后离开订阅是不好的做法。如果您在模板中仅使用异步管道进行订阅,它会自动取消订阅。
    【解决方案3】:

    如果你想使用订阅而不是异步管道,你可以使用 RxJs 操作符 takeWhile。请看下面的代码...

    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { Observable } from 'rxjs';
    import { takeWhile, map } from 'rxjs/operators';
    
    @Component({
      selector: 'my-app',
      templateUrl: './app.component.html',
      styleUrls: [ './app.component.css' ]
    })
    export class AppComponent implements OnInit, OnDestroy {
      name = 'Angular';
      isActive: boolean;
      // returned from service.  Does not need to initialized here
      thingToSubscribeTo:Observable<any> = new Observable<any>();
    
      ngOnInit() {
        this.isActive = true;
        // you can replace with call to service then pipe it.
        this.thingToSubscribeTo.pipe(
          map(res => {
            // handle subscription
          }),
          takeWhile(() => this.isActive)
        );
    
      }
    
      ngOnDestroy() {
        this.isActive = false;
      }
    }
    

    【讨论】:

    • 从技术上讲,takeUntilunsubscribe 不同,因为unsubscribe 破坏了订阅,而takeUntil 完成了订阅:reactivex.io/rxjs/class/es6/…
    • 感谢 Julien,这是有用的信息。我相信 takeUntil 解决方案也应该解决这个问题。但是,接受的解决方案可能会更好,因为它不需要在每个具有需要取消订阅的订阅的组件上实现 takeUntil。
    • 嗨,保罗,如果我的评论有误,很抱歉。你的方法是完全有效的。我只是想指出,从技术上讲,告知未来的读者不同之处是不一样的。
    猜你喜欢
    • 1970-01-01
    • 2014-04-02
    • 2019-12-30
    • 1970-01-01
    • 2018-05-06
    • 1970-01-01
    • 2018-09-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多