如何使用ngrx和effects订阅操作成功回调函数

54

我想做一件简单的事情 - 在某个实体保存后(使用 HTTP 请求),我希望导航回列表路由。问题是:如何订阅成功操作(或者可能是 reducer 或 effect?)

这里是我的 actions 代码:

static SAVE_POST = '[POST] Save POST';
savePOST(Post): Action {
    return {
        type: PostActions.SAVE_POST,
        payload: Post
    };
}

static SAVE_POST_SUCCESS = '[POST] Save POST Success';
savePOSTSuccess(Post): Action {
    console.log('action: savePostSuccess')
    return {
        type: PostActions.SAVE_POST_SUCCESS,
        payload:Post
    };
}

我正在使用 Effects:

   @Effect() savePost$ = this.update$
    .ofType(PostActions.SAVE_POST)
    .map(action => action.payload)
    .switchMap(post => this.svc.savePost(post))
    .map(post => this.postActions.savePOSTSuccess(post));

Reducer:

const initialState: PostListState = [];

export default function (state = initialState, action: Action): PostListState {
    switch (action.type) {
        case PostActions.LOAD_POST_SUCCESS: {
            return action.payload;
        }
        case PostActions.SAVE_POST_SUCCESS: {
            console.log('SavePOST SUCCESS',action.payload)
            let index = _.findIndex(state, {_id: action.payload._id});
            if (index >= 0) {
                return [
                    ...state.slice(0, index),
                    action.payload,
                    ...state.slice(index + 1)
                ];
            }
            return state;
        }
        default: {
            return state;
        }
    }
}

在我的组件中,我希望订阅成功回调函数:

   handlePostUpdated($event) {
     this.post = this.code;
     let _post: Post = Object.assign({}, { _id: this.id, name: this.name, text: this.post });
     this.store.dispatch(this.postActions.savePOST(_post)); //not have "subscribe" method

  }

感谢您的帮助

6个回答

92
您也可以订阅组件中的操作:
[...]
import { Actions } from '@ngrx/effects';
[...]

@Component(...)
class SomeComponent implements OnDestroy {
    destroyed$ = new Subject<boolean>();

    constructor(updates$: Actions) {
        updates$.pipe(
           ofType(PostActions.SAVE_POST_SUCCESS),
           takeUntil(this.destroyed$)
        )
        .subscribe(() => {
           /* hooray, success, show notification alert etc.. */
        });
    }

    ngOnDestroy() {
        this.destroyed$.next(true);
        this.destroyed$.complete();
    }
}

2
总有很多方法可供选择,但如果您只想显示一个小通知,表示保存成功,那么这种方式在我看来是最“清洁”的,因为它是一次性事件,不应该一直通过存储器进行。 - olsn
@olsn 这是一个很酷的方法。目前我已经在我的应用程序中应用了它 ;) - angularrocks.com
4
destroyed$takeUntil(...) 结合使用是一种模式,它会自动取消订阅,因此您不必跟踪订阅。 - olsn
我知道这是可能的,但这不被认为是反模式吗?我认为组件监听状态变化,而不是操作。 - Michael
1
一般来说是的,但没有一个项目是完全按照书本上的要求去做的...因为有太多原因(时间、质量、代码开销、复杂性等等),而且在我看来:警报只是其中之一——通过存储库进行操作意味着您必须实现显示消息、隐藏消息、还要将消息保存到存储库中,然后实现一些效果,从存储库中删除它等等...而更加实用的方法是直接调用第三方组件的某个函数,这样就可以省去大量的工作。 - olsn
显示剩余9条评论

59

基于此文章:https://netbasal.com/listening-for-actions-in-ngrx-store-a699206d2210,并作出一些小的修改,因为自从 ngrx 4 以来不再有 Dispatcher,而是有 ActionsSubject

import { ActionsSubject } from '@ngrx/store';
import { ofType } from "@ngrx/effects";

subsc = new Subscription();

constructor(private actionsSubj: ActionsSubject, ....) { 
  this.subsc = this.actionsSubj.pipe(
     ofType('SAVE_POST_SUCCESS')
  ).subscribe(data => {
     // do something...
  });
}

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

4
和使用 actions$: Actions 然后使用 .ofType('SAVE_POST_SUCCESS') 做有什么不同吗?我注意到这种方法会更早触发,然后是 ActionSubject。这是否意味着它只会在 reducers 运行后触发? - TrySpace
AJT的答案将在新状态初始化之前运行,因此会出现数据不匹配的情况,如何防止这种情况发生? - Sytham
8
截至2018年8月,这应该是被接受的答案 - 接受的答案现在已经过时了 - 使用这种更现代的方法。 - danday74
2
@YehiaA.Salam,它的名字就是订阅。如果我们不取消订阅,就会出现内存泄漏,即可观察的actionSubject仍然保持打开状态。 - AT82
3
那并不是它的一部分。它来自于 rxjs 库: import { Subscription } from 'rxjs'。文档链接:https://rxjs-dev.firebaseapp.com/guide/subscription :) - AT82
显示剩余4条评论

3
您需要一个选择器来获取最新的帖子。
然后,您可以订阅此可观察对象this.latestPost$并重定向到您的新路由,假设您在组件中声明了latestPost$: Observable<Post>;
要了解更多详细信息,请参见此示例应用程序:https://github.com/ngrx/example-app

2

如果您在savePOSTSuccess操作中添加reducer函数代码并展示保存的帖子如何影响您的状态,效果会更好。

通常情况下,您可以在帖子状态中添加一个名为“editing”的布尔属性。

在reducer中,您可以通过将该属性设置为false来将标志值设置为!editing状态。

然后,您可以在该属性上添加一个选择器,并根据此属性显示或隐藏表单。


你的意思是要在Post模型中添加“editing”属性吗?你说的“在该属性上添加选择器”是什么意思(抱歉,我对ngrx还不熟悉...) - happyZZR1400
好答案。让我免于写出糟糕的代码。 - Experimenter

2
当然可以。请考虑以下监听已保存联系人的示例。
export class PageContactComponent implements OnInit, OnDestroy {

  destroy$ = new Subject<boolean>();
    
  constructor(private actionsListener$: ActionsSubject) {}

  ngOnInit() {
    this.actionsListener$
      .pipe(ofType(ContactActionTypes.SAVE_SUCCESS))
      .pipe(takeUntil(this.destroy$))
      .subscribe((data: any) => {
        // Do your stuff here
      });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

在这里,我创建了一个名为actionsListener$ActionsSubject。在这个例子中,我正在监听ContactActionTypes.SAVE_SUCCESS(当联系人已保存时发生的操作)。在subscribe部分描述您的代码,并不要忘记销毁订阅。

编辑:以下是Action的样子:

export enum ContactActionTypes {
  SAVE_SUCCESS = "[Contact] Save Success",
}

export class ActionContactSaveSuccess implements Action {
  readonly type = ContactActionTypes.SAVE_SUCCESS;

  constructor(readonly payload: { contact: any }) {}
}

export type ContactActions = ActionContactSaveSuccess;

0

Angular,Angular...每次需要不同的东西时都要包含另一个插件... :)

但在这种情况下,似乎可以在没有效果的情况下完成。与@kuncevic-dev提到的相同方法。

this.store.select(state => state.CompanyLicencesState)
  .pipe(takeUntil(this.destroy))
  .subscribe((state: CompanyLicences.State) => {
  ...
});
this.store.dispatch(new GetCompanyLicences(filter))
  .pipe(
    finalize(() => (this.companyLicencsLoading = false)), 
    takeUntil(this.destroy))
  .subscribe(() => {
  ...
});

会完成工作。


this.store.dispatch() 返回 void,对吧? - Ben Taliadoros

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