Search code examples
javascriptangularrxjsngrxngrx-effects

NgRx effect, ones failure occurs, does not accept new actions


@Injectable()
export class UserEffects {
  constructor(
    private store$: Store<fromRoot.State>,
    private actions$: Actions,
    private router: Router,
    private chatService: ChatService,
    private authService: AuthService,
    private db: AngularFireAuth,
    private http: HttpClient
  ) { }

  @Effect()
  login$: Observable<Action> = this.actions$.pipe(
    ofType(UserActions.LOGIN),
    switchMap((action: UserActions.LoginAction) =>
      from(this.db.auth.signInWithEmailAndPassword(action.payload.email, action.payload.password)),
    ),
    // switchMapTo(from(this.db.auth.currentUser.getIdToken())),
    switchMapTo(from(this.db.authState.pipe(
      take(1),
      switchMap((user)=>{
        if (user)
          return from(user.getIdToken());
        else
          return of(null);
      })
    ))),
    switchMap(token => this.http.post(environment.apiUrl + '/api/login', { token: token })),
    tap((response: any) => {
      if (response.valid === 'true') {

        localStorage.setItem('token', response.token);

        this.router.navigate(['dash']);

      }
    }),
    map(response => new UserActions.LoginSuccessAction({ response })),
    catchError(error => {
      return of({
        type: UserActions.LOGIN_FAILURE,
        payload: error
      });
    })
  );

  @Effect({ dispatch: false })
  loginSuccess$: Observable<Action> = this.actions$.pipe(
    ofType(UserActions.LOGIN_SUCCESS),
    tap((action: UserActions.LoginSuccessAction) => {
      console.log("LOGIN_SUCCESS");
      console.log(action);
    }));

    @Effect({ dispatch: false })
    loginFailure$: Observable<Action> = this.actions$.pipe(
      ofType(UserActions.LOGIN_FAILURE),
      tap((action: UserActions.LoginFailureAction)=>{
        console.log("LOGIN_FAILURE");
        console.log(action.payload.code);
      }));
}

Use case: You are at a login page. You enter your username and password to call LoginAction. If all goes well, LoginSuccessAction should be called. Otherwise, LoginFailureAction should be called.

Everything works except one case:
Once LoginFailureAction is called once, neither LoginSuccessAction nor LoginFailureAction will be called again. I am at a loss of why this is happening. I think it may have something to do with the line:

catchError(error => {
  return of({
    type: UserActions.LOGIN_FAILURE,
    payload: error
  });
})

but here I am lost. I am not an expert on reactive programming, but it all works as it's supposed to except this one case. Does anyone have an idea why this occurs?


Solution

  • Effects respect successful completion and errors. In case of an error ngrx will resubscribe, in case case of a successful completion it will not.

    For example catchError can cause this case, because it doesn't listen to its parent stream. If you want to enable stream in case of an error you need to use repeat operator right after catchError.

        catchError(error => {
          return of({
            type: UserActions.LOGIN_FAILURE,
            payload: error
          });
        }),
        repeat(), // <- now effect is stable.