简体   繁体   中英

load data in ngrx effect with dispatch an action before using route guard

My auth is based on NGRX so when the page starts loading I got all roles and then get logged in.

but when I start using route guard, route gourd start working before user data get loading

how can I wait for user load action to be done then start using canActivate I try below solution but it's not working

export class AuthGuard implements CanActivate, OnDestroy {
private unsubscribe: Subject<any>;

constructor(private store: Store<AppState>, private router: Router,
  private alertService: ToastrService, private authService: AuthService) {
  this.unsubscribe = new Subject();
}
ngOnDestroy(): void {
  this.unsubscribe.next();
  this.unsubscribe.complete();
}
getFromStoreOrAPI(): Observable<any> {
  return this.store.pipe(
    select(isUserLoaded),
    tap((data: boolean) => {
      if (!data) {
        this.store.dispatch(new UserRequested());
      }
    }),
    take(1)
  );
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {

  return this.getFromStoreOrAPI().pipe(
    switchMap(() =>
      this.store.pipe(
        select(isLoggedIn),
        map(loggedIn => {
          if (!loggedIn) {
            this.router.navigateByUrl('/auth/login');
            return false;
          } else {
            this.store.pipe(
              select(currentUserRoles),
              map((userRoles: Role[]) => {
                //.......
              }),
              takeUntil(this.unsubscribe)
            ).subscribe();
          }
        }),
      )
    ),
    catchError(() => this.router.navigateByUrl('/auth/login'))
  );
}

}

You can use filter to wait until loaded flag is true .

Here is approach I took with my auth.guard.ts :

canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> {
    return this.authFacade.loaded$.pipe(
      filter(loaded => !!loaded),
      mergeMap(() => this.authFacade.userAccount$),
      map(userAccount => {
        if (!userAccount) this.authFacade.redirectLoginPage(state.url);
        return !!userAccount;
      }),
      first()
    );
  }

In my case, main app component is dispatching an action CheckAuth to check if user is already authenticated, and then set loaded flag.

It should work with some adaptation for your need. But main difference is the use of filter which avoid to continue the workflow if user checking is not done, and force waiting for the value.

Of course, be sure to set loaded value in all the case after receiving response (authenticated or not), or in case of any error.

Here is a potential adaptation for your case:

authLoaded$ = this.store.pipe(select(authLoaded));
authAccount$ = this.store.pipe(select(authAccount));

canActivate(...) {
  return userLoaded$.pipe(    
    tap(loaded => {
      if (!loaded) {
        this.store.dispatch(new UserRequested());
      }
    }),
    filter(loaded => !!loaded),
    mergeMap(() => authAccount$),
    map(authAccount => {
      if (!authAccount.loggedIn) {
        this.router.navigateByUrl('/auth/login');
        return false;
      }

      if (!authAccount.roles?.length) {
        this.router.navigateByUrl('/auth/forbidden');
        return false;
      }

      // do some extra stuff...

      return true;
    }),
    first()
  );
}

I renamed isUserLoaded to authLoaded to clearly indicate the status of authentication loading (you can use also ready or not for instance). But not necessary user.

I created also a new selector authAccount which returns an object with at least 2 things:

  • loggedIn: true/false if user is logged in
  • roles: array of user roles. But you can add of course user property, with user details.

This is a composed selector from different parts of your state. With it, your code is more clear and maintable, you receive a complete status of your current authentication user.

Maybe some typos is possible, I wrote the code directly in my answer without testing it. Hope this will help you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM