Uzair Khan
Uzair Khan

Reputation: 2970

How to unsubscribe from ngrx/store?

I have a component which gets its data from subscribing to a store.

this.store.select('somedata').subscribe((state: any) => {
  this.somedata = state.data;
});

I want to unsubscribe from this subscription when component is no more, in other places where I am subscribing to some observable, something like this:

this.service.data.subscribe(
   (result: any) => {//data}
);

I unsubscribed it on ngOnOnDestroy, like this:

ngOnDestroy(){
   this.service.data.unsubscribe();
}

But in case of store I'm not able to, it gives me error:

Property 'unsubscribe' does not exist on type 'Store<State>'

Upvotes: 24

Views: 31705

Answers (6)

Alexei - check Codidact
Alexei - check Codidact

Reputation: 23098

This answer is based on the answers provided by FlavorScape and by mahyar.

No external libraries solution

One way to avoid bloating each component with the subject and its code is to use a base component (tested with Angular 10.0.6):

base.component.ts

import { Subject } from "rxjs";
import { Component } from "@angular/core";

@Component({
    selector: "app-base-component",
    template: ""
})
export class BaseComponent {
    public ngDestroyed$ = new Subject();

    public onDestroy(): void {
        this.ngDestroyed$.next();
    }
}

foo.component.ts

@Component({
    selector: "app-foo",
    templateUrl: "./foo.component.html",
    styleUrls: ["./foo.component.scss"]
})
export class FooComponent extends BaseComponent implements OnInit, OnDestroy {

    fooList$: Observable<FooModel[]>;

    @ViewChild(DataBindingDirective) dataBinding: DataBindingDirective;
    public gridData: any[];
    public gridView: any[];

    public mySelection: string[] = [];

    constructor(private readonly store: Store<AppState>) {
        super();
    }
    ngOnDestroy(): void {
        this.onDestroy();
    }

    ngOnInit(): void {
        this.store.dispatch(ApplicationFooItemsRequestedAction());
        this.fooList$ = this.store.select(selectAllApplicationFooItems);

        this.fooList$.pipe(takeUntil(this.ngDestroyed$)).subscribe(ul => {
            // do stuff with items
        });
    }
}

Using an external library

You can use @ngneat/until-destroy library to avoid custom code and also support other scenarios (e.g. within services)

@Component({
    selector: "app-foo",
    templateUrl: "./foo.component.html",
    styleUrls: ["./foo.component.scss"]
})
export class FooComponent extends BaseComponent implements OnInit, OnDestroy {

    ngOnInit(): void {
        this.store.dispatch(ApplicationFooItemsRequestedAction());
        this.fooList$ = this.store.select(selectAllApplicationFooItems);

        this.fooList$.pipe(takeUntil(untilDestroyed(this))).subscribe(ul => {
             // do stuff with items
        });
     }

}

Upvotes: 3

Mahyar
Mahyar

Reputation: 71

The neatest way I've used is using ngx-take-until-destroy library. Your code will be something like this:

this.store.select('somedata')
    .pipe(untilDestroyed(this))
    .subscribe((state: any) => {
        this.somedata = state.data;
    });

You also need to have ngOnDestroy() method in your class.

Upvotes: 6

FlavorScape
FlavorScape

Reputation: 14319

There's a better way than the top voted answer, a way in which you don't have to manage a bunch of subscriptions, only one. Then you can have as many subscriptions as you want without having to create a bunch of unnecessary vars.

  public ngDestroyed$ = new Subject();

  public ngOnDestroy() {
    this.ngDestroyed$.next();
  }

  public ngOnInit() {
    this.myWhateverObs$
        .pipe(takeUntil(this.ngDestroyed$))
        .subscribe((val)=> { this.whatever()});
    this.myWhateverNPlusOne$
        .pipe(takeUntil(this.ngDestroyed$))
        .subscribe((val)=> { this.whatever()})
  }

Upvotes: 42

Akshay Garg
Akshay Garg

Reputation: 1040

You can get value without directly calling subscribe method, get value by async pipe like

@Component({
    template: `
        <div>Current Count: {{ counter | async }}</div>
    `
})
class MyAppComponent {
    counter: Observable<number>;

    constructor(private store: Store<AppState>){
        this.counter = store.select('counter');
    }
}

Here we are using async pipe for getting value. The async pipe subscribes to an Observable or Promise and returns the latest value it has emitted. When a new value is emitted, the async pipe marks the component to be checked for changes. When the component gets destroyed, the async pipe unsubscribes automatically to avoid potential memory leaks.

Upvotes: 9

stojevskimilan
stojevskimilan

Reputation: 1030

You don't need to subscribe in first place use | async in your template. Everything you get from store is observable, let angular handle your subscription. Here is api

Upvotes: 5

G.Vitelli
G.Vitelli

Reputation: 1287

When you subscribe you will receive a subscription object on it you can call unsubscribe()

const subscription = this.store.select('somedata').subscribe((state: any) => {
  this.somedata = state.data;
});
// later
subscription.unsubscribe();

or

ngOnInit(){
 this.someDataSubscription = this.store.select('somedata').subscribe((state: any) => {
  this.somedata = state.data;
 });
}

ngOnDestroy(){
  this.someDataSubscription.unsubscribe();
}

Upvotes: 33

Related Questions