如何取消订阅ngrx/store?

23

我有一个组件,它通过订阅存储来获取数据。

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

当组件不再存在时,我希望取消订阅此订阅。在其他订阅某个可观察对象的地方,类似这样:

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

我在ngOnDestroy上取消了它的订阅,就像这样:

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

但是当涉及到商店时,我不能够成功,它会给我错误:

Property 'unsubscribe' does not exist on type 'Store<State>'
6个回答

41

有比得到最高投票答案更好的方法,一种无需管理大量订阅,只需一个的方法。这样您就可以拥有任意数量的订阅,而无需创建大量不必要的变量。

  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()})
  }

5
谢谢你的解决方案。我在应用程序中有多个订阅,使用上述方法很麻烦。 - Raj
1
在我的情况下,我必须在ngOnDestroy中使用next()而不是complete()。 - Téwa
1
调用 this.ngDestroyed$.complete() 不会导致 takeUntil 完成订阅。相反,您需要调用 .next()。这样,您就不需要调用 complete 和 unsubscribe 了。 - Dmitriy Snitko
如果您想添加 switchMap 或其他操作符,您会在 takeUntil() 之前还是之后添加? - user12207064
2
在调用 ngDestroyed$.next() 后,必须调用 ngDestroyed$.complete(),否则将会泄漏该主题。它可能是一个小对象,但它会保持活动状态... 关于顺序:takeUntil 总是最后一个,期望的是 shareReplymulticast 和类似的多播操作符。 - Akxe
显示剩余4条评论

32

当您订阅后,您将收到一个订阅对象,您可以在该对象上调用unsubscribe()方法来取消订阅。

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

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

3
对于许多订阅,有一种更简单和不那么混乱的方法来完成这个。请查看使用 takeUntil(this.$ngDestroyed) 的答案。 - FlavorScape

8
您可以通过异步管道(async pipe)获得值,而无需直接调用 subscribe 方法,如下所示:
@Component({
    template: `
        <div>Current Count: {{ counter | async }}</div>
    `
})
class MyAppComponent {
    counter: Observable<number>;

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

在这里,我们使用异步管道(async pipe)获取值。异步管道订阅一个Observable或Promise,并返回它所发出的最新值。当新值被发出时,异步管道标记组件要检查更改。当组件被销毁时,异步管道会自动取消订阅,以避免潜在的内存泄漏。

如果您需要在.ts文件中使用“store observable”从存储片段获取值,该怎么办?在这种情况下,您需要使用订阅,对吗? - Mark
@Mark,你可以使用mapfilterswitchMap等许多rxjs操作符来修改你的存储,而无需订阅。然后,你可以使用async管道将修改后的Observable传递到模板中,就像@Akshay所描述的那样。 - matewka

5

我使用过最简洁的方法是使用ngx-take-until-destroy库。 你的代码将会像这样:

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

你还需要在类中使用ngOnDestroy()方法。


4

在模板中使用| async,你不需要首先订阅任何内容。从store获取的所有内容都是可观察的,让angular处理您的订阅。 这里是api文档


1
如果您需要在.ts文件中使用“存储可观察对象”来获取存储片段中的值,该怎么办?那么您需要在这种情况下使用subscribe,对吗? - Mark

2
这个答案基于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
        });
    }
}

使用外部库

您可以使用@ngneat/until-destroy库来避免编写自定义代码,并支持其他场景(例如,在服务中)

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

}

1
你忘记了一个非常关键的方面!你没有完成 ngDestroyed$ 主题本身。这样它将会一直保留在内存中... - Akxe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接