Angular/RxJs When should I unsubscribe from `Subscription`

前端 未结 22 2184
隐瞒了意图╮
隐瞒了意图╮ 2020-11-21 04:56

When should I store the Subscription instances and invoke unsubscribe() during the NgOnDestroy life cycle and when can I simply ignore them?

相关标签:
22条回答
  • 2020-11-21 05:16

    I like the last two answers, but I experienced an issue if the the subclass referenced "this" in ngOnDestroy.

    I modified it to be this, and it looks like it resolved that issue.

    export abstract class BaseComponent implements OnDestroy {
        protected componentDestroyed$: Subject<boolean>;
        constructor() {
            this.componentDestroyed$ = new Subject<boolean>();
            let f = this.ngOnDestroy;
            this.ngOnDestroy = function()  {
                // without this I was getting an error if the subclass had
                // this.blah() in ngOnDestroy
                f.bind(this)();
                this.componentDestroyed$.next(true);
                this.componentDestroyed$.complete();
            };
        }
        /// placeholder of ngOnDestroy. no need to do super() call of extended class.
        ngOnDestroy() {}
    }
    
    0 讨论(0)
  • 2020-11-21 05:18

    You don't need to have bunch of subscriptions and unsubscribe manually. Use Subject and takeUntil combo to handle subscriptions like a boss:

    import { Subject } from "rxjs"
    import { takeUntil } from "rxjs/operators"
    
    @Component({
      moduleId: __moduleName,
      selector: "my-view",
      templateUrl: "../views/view-route.view.html"
    })
    export class ViewRouteComponent implements OnInit, OnDestroy {
      componentDestroyed$: Subject<boolean> = new Subject()
    
      constructor(private titleService: TitleService) {}
    
      ngOnInit() {
        this.titleService.emitter1$
          .pipe(takeUntil(this.componentDestroyed$))
          .subscribe((data: any) => { /* ... do something 1 */ })
    
        this.titleService.emitter2$
          .pipe(takeUntil(this.componentDestroyed$))
          .subscribe((data: any) => { /* ... do something 2 */ })
    
        //...
    
        this.titleService.emitterN$
          .pipe(takeUntil(this.componentDestroyed$))
          .subscribe((data: any) => { /* ... do something N */ })
      }
    
      ngOnDestroy() {
        this.componentDestroyed$.next(true)
        this.componentDestroyed$.complete()
      }
    }
    

    Alternative approach, which was proposed by @acumartini in comments, uses takeWhile instead of takeUntil. You may prefer it, but mind that this way your Observable execution will not be cancelled on ngDestroy of your component (e.g. when you make time consuming calculations or wait for data from server). Method, which is based on takeUntil, doesn't have this drawback and leads to immediate cancellation of request. Thanks to @AlexChe for detailed explanation in comments.

    So here is the code:

    @Component({
      moduleId: __moduleName,
      selector: "my-view",
      templateUrl: "../views/view-route.view.html"
    })
    export class ViewRouteComponent implements OnInit, OnDestroy {
      alive: boolean = true
    
      constructor(private titleService: TitleService) {}
    
      ngOnInit() {
        this.titleService.emitter1$
          .pipe(takeWhile(() => this.alive))
          .subscribe((data: any) => { /* ... do something 1 */ })
    
        this.titleService.emitter2$
          .pipe(takeWhile(() => this.alive))
          .subscribe((data: any) => { /* ... do something 2 */ })
    
        // ...
    
        this.titleService.emitterN$
          .pipe(takeWhile(() => this.alive))
          .subscribe((data: any) => { /* ... do something N */ })
      }
    
      ngOnDestroy() {
        this.alive = false
      }
    }
    
    0 讨论(0)
  • 2020-11-21 05:19

    Since seangwright's solution (Edit 3) appears to be very useful, I also found it a pain to pack this feature into base component, and hint other project teammates to remember to call super() on ngOnDestroy to activate this feature.

    This answer provide a way to set free from super call, and make "componentDestroyed$" a core of base component.

    class BaseClass {
        protected componentDestroyed$: Subject<void> = new Subject<void>();
        constructor() {
    
            /// wrap the ngOnDestroy to be an Observable. and set free from calling super() on ngOnDestroy.
            let _$ = this.ngOnDestroy;
            this.ngOnDestroy = () => {
                this.componentDestroyed$.next();
                this.componentDestroyed$.complete();
                _$();
            }
        }
    
        /// placeholder of ngOnDestroy. no need to do super() call of extended class.
        ngOnDestroy() {}
    }
    

    And then you can use this feature freely for example:

    @Component({
        selector: 'my-thing',
        templateUrl: './my-thing.component.html'
    })
    export class MyThingComponent extends BaseClass implements OnInit, OnDestroy {
        constructor(
            private myThingService: MyThingService,
        ) { super(); }
    
        ngOnInit() {
            this.myThingService.getThings()
                .takeUntil(this.componentDestroyed$)
                .subscribe(things => console.log(things));
        }
    
        /// optional. not a requirement to implement OnDestroy
        ngOnDestroy() {
            console.log('everything works as intended with or without super call');
        }
    
    }
    
    0 讨论(0)
  • 2020-11-21 05:19

    You can use latest Subscription class to unsubscribe for the Observable with not so messy code.

    We can do this with normal variable but it will be override the last subscription on every new subscribe so avoid that, and this approach is very much useful when you are dealing with more number of Obseravables, and type of Obeservables like BehavoiurSubject and Subject

    Subscription

    Represents a disposable resource, such as the execution of an Observable. A Subscription has one important method, unsubscribe, that takes no argument and just disposes the resource held by the subscription.

    you can use this in two ways,

    • you can directly push the subscription to Subscription Array

       subscriptions:Subscription[] = [];
      
       ngOnInit(): void {
      
         this.subscription.push(this.dataService.getMessageTracker().subscribe((param: any) => {
                  //...  
         }));
      
         this.subscription.push(this.dataService.getFileTracker().subscribe((param: any) => {
              //...
          }));
       }
      
       ngOnDestroy(){
          // prevent memory leak when component destroyed
          this.subscriptions.forEach(s => s.unsubscribe());
        }
      
    • using add() of Subscription

      subscriptions = new Subscription();
      
      this.subscriptions.add(subscribeOne);
      this.subscriptions.add(subscribeTwo);
      
      ngOnDestroy() {
        this.subscriptions.unsubscribe();
      }
      

    A Subscription can hold child subscriptions and safely unsubscribe them all. This method handles possible errors (e.g. if any child subscriptions are null).

    Hope this helps.. :)

    0 讨论(0)
  • 2020-11-21 05:19

    The SubSink package, an easy and consistent solution for unsubscribing

    As nobody else has mentioned it, I want to recommend the Subsink package created by Ward Bell: https://github.com/wardbell/subsink#readme.

    I have been using it on a project were we are several developers all using it. It helps a lot to have a consistent way that works in every situation.

    0 讨论(0)
  • 2020-11-21 05:22

    --- Update Angular 9 and Rxjs 6 Solution

    1. Using unsubscribe at ngDestroy lifecycle of Angular Component
    class SampleComponent implements OnInit, OnDestroy {
      private subscriptions: Subscription;
      private sampleObservable$: Observable<any>;
    
      constructor () {}
    
      ngOnInit(){
        this.subscriptions = this.sampleObservable$.subscribe( ... );
      }
    
      ngOnDestroy() {
        this.subscriptions.unsubscribe();
      }
    }
    
    1. Using takeUntil in Rxjs
    class SampleComponent implements OnInit, OnDestroy {
      private unsubscribe$: new Subject<void>;
      private sampleObservable$: Observable<any>;
    
      constructor () {}
    
      ngOnInit(){
        this.subscriptions = this.sampleObservable$
        .pipe(takeUntil(this.unsubscribe$))
        .subscribe( ... );
      }
    
      ngOnDestroy() {
        this.unsubscribe$.next();
        this.unsubscribe$.complete();
      }
    }
    
    1. for some action that you call at ngOnInit that just happen only one time when component init.
    class SampleComponent implements OnInit {
    
      private sampleObservable$: Observable<any>;
    
      constructor () {}
    
      ngOnInit(){
        this.subscriptions = this.sampleObservable$
        .pipe(take(1))
        .subscribe( ... );
      }
    }
    

    We also have async pipe. But, this one use on the template (not in Angular component).

    0 讨论(0)
提交回复
热议问题