Search code examples
angularngrxngrx-effects

Angular 9 + NGRX: Best way to catch effect completion in component


I have a form to create and account: email, name, password ... and I want the app to display a message to the user when the creation is successful , more precisely when effect create the action that tells the user is created.

  • When the user creation form is submitted, I dispatch and action to the store with the content of the form.
  • I have an effect triggering when that action is dispatched.
  • The effect is calling a service that calls an http api in my backend that creates the account in database.
  • When the service is done, the effect dispatch a new action 'UserCreated'

My question is: What is the best way to tell the component that the user is correctly created?

For the moment I have done this:

  • When the UserCreated action is called after service is done, I update the store with the id of the newly created user.

  • I subscribe to a selector in ngOnInit that sends the value of the id: if it's not empty I display a message.

I have the feeling that is not the best way to do this: I would like to avoid setting an id (or a boolean true/false) to the store that uses a selector.

I would like the component to subscribe to the action: UserCreated instead, I feel that is cleaner (I might think wrong too).

I have seen some posts on action subscribing but it's not working for me (the posts were related to older version of angular and methods didn't exists anymore).

Submitting form: createaccount.component.ts

this.store.dispatch(new UserServerCreation({ user: _user }));

createaccount.action.ts

export class UserServerCreation implements Action {
readonly type = UserActionTypes.UserServerCreation;
constructor(public payload: { user: User }) { }

createaccount.effects.ts

@Effect()
createUser$ = this.actions$
  .pipe(
    ofType<UserServerCreation>(UserActionTypes.UserServerCreation),
    mergeMap(({ payload }) => {
      return this.userService.createUser(payload.user).pipe(
        tap(res => {
          this.store.dispatch(new UserCreated({ user: res.payload }));
        }),
        catchError(error => {
          this.snackBar.open(this.translateService.instant(error.error.errors.message), 'Error', {
            duration: 5000,
          });
          return of([]);

        }),
      );
    }),
  );

createaccount.selector.ts

export const selectLastCreatedUserId = createSelector(
  selectUsersState,
  usersState => usersState.lastCreatedUserId
);

Selector subscription in createaccount.component.ts

ngOnInit() {
  this.store.pipe(select(selectLastCreatedUserId)).subscribe(newId => {
    console.log('ID: ' + newId);
    if (newId) {
      const message = this.translateService.instant('USER.CREATED');
      this.layoutUtilsService.showActionNotification(message, MessageType.Create, 5000, true, false);
      this.userForm.reset();
    }
  });
}

UPDATE SOLUTION:

Finally achieved to subscribe to action channel (this was my preferred method over snackbar in the effect)

On my createaccount.component.ts I add those samples: variable:

destroy$ = new Subject<boolean>();

in the constructor:

private actionsListener$: ActionsSubject

then in ngOnInit:

this.actionsListener$
  .pipe(ofType(UserActionTypes.LoginOnDemandCreated))
  .pipe(takeUntil(this.destroy$))
  .subscribe((data: any) => {
    // Do your stuff here
    console.log('DATA : ' + JSON.stringify(data)); //Just for testing purpose
  });

Finally in ngOnDestroy:

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

}

Thanks for help !


Solution

  • I know two way:

    1. Create another effect with { dispatch: false } which means the effect doesn't dispatch any action but can do some logic, like open a Snackbar. (ofType<UserServerCreated (UserActionTypes.UserServerCreated)) and add simply a tap operator function, and do the same as in the catchError.
    2. You can subscribe also to your component to the action's channel. So you can filter this channel like in the effects. And do the same logic, as before, or save this and do a message in the component. Get error response from ngrx@effects to component