NGRX 7-即使在ngrx / effects中调度不同类型的新动作时,也陷入无限循环

时间:2019-04-28 04:31:40

标签: angular rxjs rxjs6 ngrx-store ngrx-effects

我正在尝试在@ angular / core 7.1.0和@ ngrx / store 7.0中实现登录。现在的问题是,当我从登录组件中调度新的Login操作时,它可以正确地监听Login效果,但是即使在调度新的LoginSuccess操作时,登录操作也会陷入无休止的循环中,直到LoginFailure操作发生为止。(当我停止后端时服务)。

auth.effects.ts

  @Effect()
  login$: Observable<Action> = this.actions$.pipe(
    ofType(AuthActionTypes.LOGIN),
    map((action: Login) => action.payload),
    switchMap(payload => {
      console.log('SwitchMap: Login [Effect]: payload', payload);
      return this.authService.login(payload.email, payload.password).pipe(
        map((loginResponse: LoginResponse) => {
          console.log('loginResponse:', loginResponse);
          return new LoginSuccess(loginResponse);
        }),
        catchError(error => {
          console.log(error);
          return of(new LoginFailure({ error: error }));
        })
      );
    })
  );


  @Effect({ dispatch: false })
  loginSuccess: Observable<any> = this.actions$.pipe(
    ofType(AuthActionTypes.LOGIN_SUCCESS),
    map((action: LoginSuccess) => action.payload),
    tap((loginResponse: LoginResponse) => {
      console.log('Login_Success [Effect]: payload', loginResponse);
      localStorage.setItem('accessToken', loginResponse.accessToken);
      localStorage.setItem('refreshToken', loginResponse.refreshToken);
      localStorage.setItem('user', JSON.stringify(loginResponse.user));
      // if (loginResponse.user.isSuperAdmin) {
      //   this.router.navigate(['/admin/dashboard']);
      // } else {
      //   this.router.navigate(['/dashboard']);
      // }
    })
  );

login.component.ts

onSubmit() {
    // Will triggered only when form is submitted
    if (this.loginForm.valid) {
      console.log('Form Submitted: values', this.loginPayload);
      this.store.dispatch(new Login({ email: this.loginPayload.username, password: this.loginPayload.password }));
      this.loginForm.resetForm();
    }
  }

enter image description here

修改:新发现 当我从authService返回http调用时,可以观察到,如:

return this.http.put<LoginResponse>('/api/v1/entrance/login', body);

此错误正在发生(即请求陷入无限循环)。但是,当我通过像下面这样重新调整新的observable来伪造api时,事实并非如此。

   return new Observable<LoginResponse>((observer) => {
      if (email === 'superadmin@xyz.com' && password === 'abc123') {
        const data: LoginResponse = {
          accessToken: 'dadsfjhsjdahlfjh#324jk34h23343kkjlsadsads',
          refreshToken: 'jfjsdg-32432-sdf4543-sdff4234-3424-3434',
          user: {
            email: 'superadmin@xyz.com',
            name: 'Superadmin',
            isSuperAdmin: true,
            id: 1,
            isLdapUser: false,
            isAdUser: false,
            lastSeenAt: new Date().getTime()
          }
        };
        observer.next(data);
      } else {
        observer.error({ error: 'invalid credentials.' });
      }
      observer.complete();
    });

2 个答案:

答案 0 :(得分:0)

尝试使用此代码

  // Listen for the 'LOGIN' action
  @Effect()
  login$: Observable <Action> = this
    .actions$
    .pipe(ofType<authAction.Login>(authAction.LoginActionTypes.LOGIN), mergeMap(action => this.authService.login(action.payload).pipe(
    // If successful, dispatch success action with result
    map(data => ({type: authAction.LoginActionTypes.LOGIN_SUCCESS})),
    // If request fails, dispatch failed action
    catchError(() => of({type: authAction.LoginActionTypes.LOGIN_FAIL})))));

  /* Pass { dispatch: false } to the decorator to prevent dispatching.
  Sometimes you don't want effects to dispatch an action, for example when you only want to log or navigate.
  But when an effect does not dispatch another action, the browser will crash because the effect is both 'subscribing' to and 'dispatching'
  the exact same action, causing an infinite loop. To prevent this, add { dispatch: false } to the decorator. */
  @Effect({dispatch: false})
  loginSuccess$ = this
    .actions$
    .pipe(ofType(authAction.LoginActionTypes.LOGIN_SUCCESS), tap(() => this.router.navigate(['/portal'])));

您不需要使用return this.authService.login或return动作类型,这种方法更好,更干净,因为您不必在每次要分配动作类型时都使用new

我的意见也许是this.loginForm.resetForm();行触发您的动作被分派多次,所以我建议您将其更改为

onSubmit() {
    // Will triggered only when form is submitted
    if (this.loginForm.valid) {
      console.log('Form Submitted: values', this.loginPayload);
      this.store.dispatch(new Login({ email: this.loginPayload.username, password: this.loginPayload.password }));
    }
   this.loginForm.resetForm();
  }

我的模拟登录服务

  login(userCredentials: any): Observable<any> {
    if (userCredentials.account !== 'test') {
      return throwError('Invalid username or password');
    }
    return of({name: 'User'}); //change your code here
  }

我正在使用return()来让Observable知道我已经收到了我需要的数据,因此实际上不需要再订阅Observable流了。

如果需要帮助,请告诉我

答案 1 :(得分:0)

经过大量调试,最后我发现了错误: 它在我的authorizeRequest拦截器中。以前我的拦截器代码是:

   intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return this.store.select('auth').pipe(
      switchMap((authState: fromAuth.State) => {
        if (authState.user && authState.accessToken) {
          const secureReq = req.clone({
            headers: req.headers.set('Authorization', 'Bearer ' + authState.accessToken)
          });
          return next.handle(secureReq);
        } else {
          return next.handle(req);
        }
      })
    );
  }

在这种情况下,每当auth状态更改时,都会调度新请求,从而导致无限循环。为了解决这个问题,我必须使用 take(1)运算符

所以我的代码现在变成:

   intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return this.store.select('auth').pipe(
      take(1),
      switchMap((authState: fromAuth.State) => {
        if (authState.user && authState.accessToken) {
          const secureReq = req.clone({
            headers: req.headers.set('Authorization', 'Bearer ' + authState.accessToken)
          });
          return next.handle(secureReq);
        } else {
          return next.handle(req);
        }
      })
    );
  }