Как отписаться от ngrx / store?

У меня есть компонент, который получает данные от подписки на магазин.

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

Я хочу отказаться от подписки на эту подписку, когда компонента больше нет, в других местах, где я подписываюсь на некоторые наблюдаемые объекты, например:

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

Отписался на ngOnOnDestroy, вот так:

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

Но в случае хранения я не могу, это дает мне ошибку:

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

person Michael Philips    schedule 26.11.2017    source источник


Ответы (6)


Когда вы подпишетесь, вы получите объект подписки на нем, который вы можете вызвать 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();
}
person G.Vitelli    schedule 26.11.2017
comment
Для многих подписок есть более простой и менее сложный способ сделать это. См. Ответ с takeUntil(this.$ngDestroyed) - person FlavorScape; 06.11.2018

Есть способ лучше, чем ответ, получивший наибольшее количество голосов, способ, при котором вам не нужно управлять кучей подписок, а только одной. Тогда вы можете иметь столько подписок, сколько хотите, без необходимости создавать кучу ненужных варов.

  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()})
  }
person FlavorScape    schedule 17.10.2018
comment
спасибо за решение. у меня несколько подписок в приложении. а использование описанного выше метода - кошмар. - person Raj; 21.10.2018
comment
Это должен быть правильный ответ, его легко использовать с несколькими подписками. - person Bogdan Constantinescu; 06.12.2018
comment
В моем случае мне пришлось использовать next () вместо complete () в ngOnDestroy - person Téwa; 10.12.2018
comment
вызов this.ngDestroyed$.complete() не приведет к тому, что takeUntil завершит подписку. вместо этого вы должны позвонить .next(). Тогда вам не нужно будет звонить и отказываться от подписки. - person Dmitriy Snitko; 13.03.2019
comment
Если вы хотите добавить switchMap или другой, вы бы добавили его до или после takeUntil()? - person ; 09.12.2019
comment
@altu обычно takeUntil будет последним - person FlavorScape; 29.01.2020
comment
'создать кучу ненужных варов.' это не правильно. Подписки создаются при каждом вызове метода .subscribe, поэтому в решении, получившем наибольшее количество голосов, не создаются дополнительные переменные. Если вы не хотите отслеживать переменную для каждой подписки, вы можете создать объект subscriptions: Subscription []. - person Jacopo Lanzoni; 17.02.2020
comment
@JacopoLanzoni Я бы назвал совершенно ненужным создание переменной для каждой подписки или их массива. - person FlavorScape; 24.03.2020
comment
Нужно позвонить ngDestroyed$.complete() после ngDestroyed$.next(), иначе вы потеряете тему. Это может быть небольшой объект, но он будет напоминать об активном ... О порядке: takeUntil всегда последний, с ожиданием shareReply, multicast и подобных операторов многоадресной рассылки. - person Akxe; 23.12.2020

Вы можете получить значение без прямого вызова метода подписки, получить значение с помощью асинхронного канала, например

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

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

Здесь мы используем асинхронный канал для получения значения. Асинхронный канал подписывается на Observable или Promise и возвращает последнее значение, которое он испустил. Когда выдается новое значение, асинхронный конвейер отмечает компонент, который нужно проверить на наличие изменений. Когда компонент уничтожается, асинхронный конвейер автоматически отменяет подписку, чтобы избежать потенциальных утечек памяти.

person Akshay Garg    schedule 26.11.2017
comment
Что, если вам нужно использовать наблюдаемое хранилище внутри вашего файла .ts, чтобы получить значение из фрагмента хранилища? В этом случае вам нужно будет использовать подписку, не так ли? - person Mark; 04.10.2018
comment
@Mark вы можете использовать map, filter, switchMap и многие другие операторы rxjs, чтобы изменять ваш магазин сколько угодно, без необходимости подписки. Затем вы можете передать свой измененный Observable в шаблоне с async pipe, как описано в @Akshay. - person matewka; 17.06.2019

Вам не нужно подписываться, прежде всего используйте | async в своем шаблоне. Все, что вы получаете в магазине, можно наблюдать, позвольте angular обрабатывать вашу подписку. Вот api

person stojevskimilan    schedule 26.11.2017
comment
Что, если вам нужно использовать наблюдаемое хранилище внутри вашего файла .ts, чтобы получить значение из фрагмента хранилища? В этом случае вам нужно будет использовать подписку, не так ли? - person Mark; 04.10.2018


Этот ответ основан на ответах, предоставленных FlavorScape и пользователя mahyar.

Решение без внешних библиотек

Один из способов избежать загромождения каждого компонента предметом и его кодом - использовать базовый компонент (протестирован с помощью 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
        });
    }
}

Использование внешней библиотеки

Чтобы избежать использования специального кода, а также поддерживать другие сценарии ( например, в рамках услуг)

@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
        });
     }

}
person Alexei - check Codidact    schedule 05.09.2020
comment
Вы забыли очень важный аспект! Вы не завершили ngDestroyed$ саму тему. Таким образом он будет храниться в памяти ... - person Akxe; 23.12.2020