Search code examples
angularionic-frameworkpromiseroutesangular-route-guards

Waiting for a value to return in Angular route guards


I'm using Ionic with Angular. Inside one of my route guards, i need to see if a firestore document is created or not and the return a boolean value for it and this takes some time to get the response from the server however the route guard does not wait for the value to return and runs normally, so the page is not protected! How can i make the route guard wait for the value? or, return the value before the route guard runs? this is where i send the request to firestore:

export class FirebaseService {

  ex: Promise<boolean>;
  categoryDone: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
  constructor(private afs: AngularFirestore) {
    this.ex = new Promise((resolve) => {
          this.afs.collection('users').doc(this.user.uid).collection('categories').get().toPromise().then(
            data => {
              if (data.size > 0) {
                resolve(true)
              } else {
                resolve(false)
              }
            }
          )
        })
        this.ex.then(data=>{
          if (data) {
            this.categoryDone.next(true)
          } else {
            this.categoryDone.next(false)
          }
        })
  }

And this is my route guard code where i use it for my page:

export class CategoryDoneGuard implements CanActivate {
  categoryDone: boolean;
  constructor(private router: Router, private firebaseService: FirebaseService) {
    this.firebaseService.categoryDone.asObservable().subscribe(
      categoryDone => this.categoryDone = categoryDone
    )
  }
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | boolean {
      console.log(this.categoryDone,"Category Done VAriable in CategoryDoneGuard")
      setTimeout(() => {
        console.log(this.categoryDone,"Catgeory") //true because i waited 1 second
      }, 1000);
    if (!this.categoryDone) { //variable is undefined here 
      console.log('You are in InfoDoneGuard')
      return true
    }
    this.router.navigate(['tabs/tab-profile'])
    return false
  }

}

Solution

  • Just like in other answer, auth guard can accept an boolean, observable or promise of boolean or urltree. But I would restructure that whole service function a bit, unless you need categoryDone for something else, but you can add that anyway in your service if you need it.

    But as your service currently sits, you are calling toPromise() but also creating a new promise, which doesn't really make sense.

    I would do...

    Service:

    // please choose a more suitable name :D 
    getBool() {
      return this.afs.collection('users').doc(this.user.uid).collection('categories').get().pipe(
        map(data => data.size > 0),
        // if you need
        // tap(bool => this.categoryDone.next(bool))
      )
    }
    

    Can Activate:

    canActivate(...): Observable<boolean> {
      return this.firebaseService.getBool().pipe(
        map(bool => {
          if (bool) {
            return true;
          }
          this.router.navigate(['tabs/tab-profile'])
          return false;
        })
      )
    }
    

    But if categoryDone is essential for you in your app, you can also subscribe to it in the authguard like in MoxxiManagarm's answer. Anyhow I would restructure the firebase query to remove the promise stuff.