2

When I reload my page, it always goes to blank page instead of the same page. The reason behind this is canActivate method which is cheking for user's permission gets invoked as soon as user refreshes the page and it is not able to get user data immediately. User data comes after some seconds. Is there any way to make canActivate method wait till data comses? I have been going through RxJS's page but not understanding how to do it. Can someone suggest me any ideas?

  constructor(private router: Router, private userDataService: UserDataService) {
    userDataService.userData$.subscribe(
      (data: User) => {
        this.userData = userData --> comes after some seconds
      }
    );
  }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):         
     Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree 
  {
    const result = this.getUserData(route);
    if (!result) {
      this.blankPage();
    }
    return result;
  }


public getUserData(): Observable<boolean> {
    if (this.userData) {
        check for permissions...
        return of(true);    
    }
    return of(false);
}

userdata.service.ts:

  userDataSubscription: Subscription;
  userData: BehaviorSubject<User> = new BehaviorSubject<User>(null);
  public userData$: Observable<User> = this.userData.asObservable();
  
  
  private getUserData(): void {
    const userData$ = new Observable(
      subscriber => subscriber.next(new User())
    ).pipe(
      delay(1000),
      mergeMap((model: User) => this.getUserInfo(model)),
      mergeMap((model: User) => this.getUserPermission(model)),
      catchError((err, caught) => {
        console.error('failed')
        return caught;
      })
    );
    this.userContextDataSubscription = userData$.subscribe(
      (data: User) => {
        this.userData,next(model);
      },
      () => console.error('failed')
    );
  }
Radiant
  • 360
  • 3
  • 26
  • "The same page" does not exist anymore once you hit a (hard) refresh. From that point on the browser is responsible again and will show a blank page until your application gives it something to display initially. Your best bet seems to be a loading-screen that's showing until the angular app loaded (or a route of any kind). – Philipp Meissner Sep 15 '21 at 14:14
  • @PhilippMeissner Thanks for the reply. I am anyways showing loadinScreen but in route it will be blank after loading is finished . and that's why it is not going back to my old route. it goes to localhost:4200/#/ instead of localhost:4200/#/my-comp – Radiant Sep 15 '21 at 14:16
  • On a first glance I noticed that you manually subscribe to the observable. No need - Just have your `canActivate` return an observable that resolves to a `boolean`. Something like: https://gist.github.com/PhilippMeissner/ccfc298a6e7c9ab0b22b4a261ed022f3 – Philipp Meissner Sep 15 '21 at 14:21
  • @PhilippMeissner. let me try this.. – Radiant Sep 15 '21 at 14:23
  • @PhilippMeissner After refresh, it still goes to localhost:4200/#/ – Radiant Sep 15 '21 at 14:27
  • Then it is a deeper issue with your routing and guards. You may have to further update the question with related code. – Philipp Meissner Sep 15 '21 at 14:28
  • @PhilippMeissner.. i have added a method which I am subscribing – Radiant Sep 15 '21 at 14:35

2 Answers2

1

You probably want to use an Angular resolver here instead of a guard. A resolver ensures data is available and loaded before a route loads.

There are lots of useful tutorials, just search for "Angular Resolver".

A resolver returns an Observable. You probably want to use the RxJS filter and take(1) operators. Something like this:

resolve(
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot
): Observable<any>|Promise<any>|any {
  return this.service.getMyData(route.paramMap.get('id')).pipe(
    filter(x => !!x),
    take(1)
  )
}

Filter here ensures the data is a truthy value. Then take(1) basically says I got what I need. All the best.

danday74
  • 52,471
  • 49
  • 232
  • 283
  • Thanks a lot for this but unfortunately, I can not use Resolver but I will go through it for my understanding – Radiant Sep 15 '21 at 15:02
0

canActive is an asynchronous method it means you can return Observeble or Promise and the route will not be loaded until Observable/Promise gets resolved here is an example of correct canActivate usage:

     canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):         
         Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree 
      {
        return this.userDataService.userData$
          .pipe(take(1))
          .pipe(map(user => {
            let result: boolean
            // check your permissions
            if (!result) {
               this.redirectToBlankPage();
               return false;
             }
             return result;
          }));
      }
icekson
  • 363
  • 1
  • 3
  • you mean I need to remove my subscription from the constructor and put it in canActivate method? – Radiant Sep 15 '21 at 14:56
  • @Radiant you do not need to subscribe in the constructor and save results to the class field since user data there can be resolved after `canActivate` will be executed, so when `canActivate` is called userData is always empty for you and the blankPage() will be always called (is it your problem if I'm correct) – icekson Sep 15 '21 at 16:39
  • Exactly...when I reload the page, userData is always null/undefined/empty and after some seconds, it comes. Meanwhile canActivate already got false as a response and route goes to blank page and url is localhost:4200/#/ instead of localhost:4200/#/my-comp – Radiant Sep 15 '21 at 16:53
  • @icekson..acording to your answer, it does not show blank page any more but the page where there is no security guard applied. This could work but is there any way to wait for the userData to come and then load the same page again instead of some other page? – Radiant Sep 16 '21 at 06:51