6

Does the Angular router have any restrictions to be used inside an NgRx effect?

I just started learning NgRx and I have the following code:

@Effect() public authenticate$ = this.actions$
    .ofType(authenticationActions.AUTHENTICATE)
        .switchMap((action: AuthenticateAction) => this.authenticationService.authenticate(action.payload)
            .map((data: TokenData) => {
                const user: User = {
                    token: data.token,
                    username: 'dummy',
                };
                console.log(data);
                this.router.navigateByUrl('/');
                return new authenticationActions.AuthenticateSuccessAction(user);
            })
            .catch(error => { console.log(error); return Observable.throw(error); })
        );

The console logs the data variable and the AuthenticateSuccessAction action is being triggered, so the router line is being executed but the navigation doesn't happen.

  • This should work normally, maybe try a different URL to make sure that its not a bug before you're navigating to root? this.router.navigateByUrl('/login'); Ensure you're not using something like an AuthGuard that redirects you back to your login screen. – Joshua Chan May 28 '18 at 14:40 
  • I already tried different urls and also disabled all guards and created a test button in the login page to use that same statement and it navigates. The problem is that statement inside the effect. This is very very strange – RBasniak May 28 '18 at 16:49
  • I have similar implementation as you and am able to route my app without problem. This is silly but, make sure you've injected the router in your constructor. – Joshua Chan May 28 '18 at 18:24
  • 1
    do the navigation within tap operator (in older versions of rxjs it was called do). – dee zg May 29 '18 at 16:58

2 Answers     正确答案

15
@Effect() public authenticate$ = this.actions$.pipe(
    ofType(authenticationActions.AUTHENTICATE),
     map(action => action.payload),
    exhaustMap((auth: any) => 
      this.authenticationService.authenticate(auth)
        .map((data: TokenData) => {
            return user: User = {
                token: data.token,
                username: 'dummy',
            };
        }).catch(error => { console.log(error); return Observable.throw(error); 
       }).pipe(
          map(user =>new authenticationActions.AuthenticateSuccessAction(user))
        )
    );)

  @Effect({ dispatch: false })
   loginSuccess$ = this.actions$.pipe(
     ofType(authenticationActions.AuthenticateSuccessAction),
     tap(() => this.router.navigate(['/']))
   );

Use exhaustMap and when you dispatching 'AuthenticateSuccessAction' action, do another effect for redirecting.

Personally, I like to separate all the services from effects, then you can use catchError() operator after success login for dispatching another action in case of failure login.

hope this works. PS: I did not verify this answer but logic is like this.

1

There should be an approach allowing not to create separate effect for redirect, something like

this.actions$.pipe(
  ofType(authenticationActions.AUTHENTICATE),
  switchMap(action =>
    this.authenticationService.authenticate(action.payload).pipe(
      map(data => new authenticationActions.successAction(data)),
      tap(() => this.router.navigate(['/'])),
      catchError(error => new authenticationActions.failAction(error))
    )
);

The thing is that tap will not be invoked if the service call failed, both map and tap will be skipped in favor of catchError if the failure case.


来自   https://stackoverflow.com/questions/50566128/angular-router-navigation-inside-ngrx-effect