Search code examples
angulartypescriptreduxngrx

Ngrx action does not complete after an error is executed


Action does not complete correctly after an error occurs

In my application I created an action to delete a user, however before deleting this user is informed a Dialog on the screen asking to inform the administrator password, if the administrator password is correct then it deletes the user.

My problem is when the administrator password is incorrect incorrect I call an action to say that the password entered is not valid and after that moment I can no longer complete the action by entering a valid password.

Sequence of shares:

[User] delete user;

[Auth] reauthenticate user;

[Auth] reauthenticate error (password is invalid);

[Auth] reauthenticate user

[Auth] reauthenticate user

[Auth] reauthenticate user

NO SUCCESS AND NO ERROR

Problem: After this occurs when I try to call the reauthentication action again with a valid password, only the Reauthenticate function is called but it is not detected that this action completed successfully.

I have seen in some source information about using Ngrx that in a way how an @Effect is created to listen to the call of the action, after the call is made and an error occurs this action can no longer be called again returning success.

Can this be true?

usuarios.effects.ts:

@Effect()
deleteUser$: Observable<Action> = this._action$.ofType(userActions.DELETE)
    .map((action: userActions.Delete) => action)
    .switchMap(data => {
        return this._userService.deleteUser(data.payload);
    })
    .map(() => new userActions.DeleteSuccess())
    .catch((err) => {
        return Observable.of(new userActions.DeleteError({error: err}));
    });

usuarios.service.ts:

deleteUser(user: IUser): Observable<void> {

    const reautenticateUser = this._modalService.create({
        nzTitle: 'Delete user',
        nzContent: ReauthenticateUserComponent,
        nzFooter: [{
            type: 'danger',
            label: 'Cancel',
            onClick: (componentInstance) => {
                componentInstance.closeDialog();
            }
        },
        {
            type: 'primary',
            label: 'Ok',
            onClick: (componentInstance) => {
                componentInstance.reauthenticateUser();
            },
            disabled: (componentInstance) => {
                return componentInstance.adminPassword.invalid;
            },
        }
        ]
    });

    return reautenticateUser.afterClose
        .switchMap((authenticated: boolean) => {

            if (authenticated) {
                const ref = this._angularFirestore
                    .doc<IProfissional | IRecepcionista>(`users/${usuario.id}`);

                return Observable.fromPromise(ref.delete()).map(() => {
                    this._message.info('User deleted success', {
                        nzDuration: 5000,
                        nzPauseOnHover: true,
                        nzAnimate: true
                    });
                });
            }

            throw new Error('Password invalid');
        });
}

NOTE: When the action of deleting a user is completed successfully, I can perform the action again without any problem.

Running in the sequence below, I have no problems:

[User] delete user;

[Auth] reauthenticate user;

[Auth] reauthenticate success (password is valid);

[User] delete user success;

[User] delete user;

[Auth] reauthenticate user;

[Auth] reauthenticate success (password is valid);

CONTINUE ...


Solution

  • This is because the Observable completes when it catches an error. You have to catch the error internally so that the external Observable continues to emit:

    .mergeMap(data =>
        this._userService.deleteUser(data.payload)
        .map(() => new userActions.DeleteSuccess())
        .catch((err) =>
            Observable.of(new userActions.DeleteError({error: err}))
        )        
    )
    

    I've switched to mergeMap you probably don't want to cancel a delete action with another delete action. Also, if you want to use RxJS 6:

    mergeMap(data =>
        this._userService.deleteUser(data.payload).pipe(
            map(() => new userActions.DeleteSuccess()),
            catchError((err) =>
              of(new userActions.DeleteError({error: err}))
            }),
        )
    )