Skip to content

Can Activate Guard for multiple config files not “working” after reload

I have an Ionic app with 2 parts. One main/home and a custom splash screen

I’ve made the splash to load all the config files and data that the main app will need. And has to be done before Home starts.

Before this new attempt I used a function with an IF and many tests. This function is called every time a different file load, and its respective flag changed until all flags are true, and Home page can load.

PRIOR SPLASH SCREEN

private initConfig() {
    this.loadClasses();
    this.loadNotes();

    ...

}

private loadClasses(){
    this.configService.loadClasses()
        .subscribe(data => {
            //data process
            this.lock.hasClasses = true;
            this.checkConfigReadiness();
        });
}

...
//Every other load method following the same pattern above


private checkConfigReadiness(){
    if(
        this.lock.hasClasses &&
        this.lock.hasNotes &&
        this.lock.hasKeywords &&
        this.lock.hasLangs &&
        this.lock.hasTypes &&
        this.lock.hasUserAchievs &&
        this.lock.hasUserCollection
    ){
        //navigateHome
    }
}

I wanted to try a more elegant solution with CanActivate guard, but I’m on a crossroad in which I don’t know if what I want is possible or if its not possible at all or maybe my idea of how Guards work is completely wrong.

SPLASH NOW

ngOnInit() {

    this.configService.isReady().subscribe(isReady => {
        if (isReady) {
            this.navCtrl.navigateRoot('/main/home');
        }
    });

    this.initConfig();
}

  private initConfig() {
    this.configService.loadTypes();

    ...
  }

CONFIG SERVICE

private lock: AccessLock = new AccessLock();
private isConfigReady: BehaviorSubject<boolean> = new BehaviorSubject(false);

private classes: BehaviorSubject<BaseConfigItem[]> = new BehaviorSubject([]);
...

isReady(): Observable<boolean> {
    return this.isConfigReady.asObservable();
}

private checkConfigReadiness(){
    if(
        this.lock.hasClasses &&
        this.lock.hasNotes &&
        this.lock.hasKeywords &&
        this.lock.hasLangs &&
        this.lock.hasTypes &&
        this.lock.hasUserAchievs &&
        this.lock.hasUserCollection
    ){
        this.isConfigReady.next(true);
    } else {
        this.isConfigReady.next(false);
    }
}

loadClasses(): Promise<any> {
    return this.getClassesFileJSON() //method return changed with .toPromise()
        .then(data => {
            this.classes.next(data);
            this.lock.hasTypes = true;
            this.checkConfigReadiness();
        })
        .catch(e => { throw e })
}

...
//Every other load method following the same pattern above

CONFIG CAN ACTIVATE GUARD

constructor(
    private configService: ConfigService,
    private navCtrl: NavController
) { }

canActivate(): Observable<boolean> | boolean {

    const isReady = this.configService.isReady();
    //ALWAYS TRUE HERE

    if (isReady) {
      return true;
    }

    this.navCtrl.navigateRoot('/splash');
    return false;
}

APP ROUTING

const routes: Routes = [
  {
    path: 'main',
    loadChildren: () => import('./pages/main/main.module').then(m => m.MainPagesModule),
    canActivate: [ ConfigCanActivateGuard ]
  },
  {
    path: 'splash',
    loadChildren: () => import('./pages/splash/splash.module').then( m => m.SplashPageModule)
  },
  {
    path: '',
    redirectTo: 'splash',
    pathMatch: 'full'
  },
];

The situation now:

The app start loading the files, once every file is loaded the app progress to Home Page, Ok!

But if I reload the Home Page, the app don’t go to Splash screen and restart the loading process to return Home. As I thought it should because of the CanActivateGuard..

Any clarification or adjustments on best practices?

====== Edition with a mofication on ConfigGuard =====

canActivate(): Observable<boolean> {

    const isReady = this.configService.isReady();

    isReady.subscribe(
        isReady => {
          if (isReady) {
            return true;
          }
          this.navCtrl.navigateRoot('/splash');
          return false;
        },
        error => {
          console.log(error);
          this.navCtrl.navigateRoot('/splash');
          return false;
        }
      );

      return isReady;
  }

Now its working. If the app is on the Home page, it will navigate back to Splash and load the config as it needs.

Answer

The canActivate method allows to return multiple types. One of which is Observable<boolean>. You can use this to your advantage and return an observable stream based on your configService.isReady() method.

canActivate(): Observable<boolean> {
    return this.configService.isReady().pipe(
      map((isReady) => {
        if (isReady) {
          return true;
        }

        this.navCtrl.navigateRoot('/splash');
        return false;
      }),
      // This path will be executed if `configService.isReady()` throws an error of any kind.
      catchError((error) => {
        console.log(error);
        this.navCtrl.navigateRoot('/splash');
        return false;
      }),
    );
  }

This way you can block the route until isConfigReady eventually emits true. When it emits false, a redirect to /splash will happen.

That’s one approach to what you want to accomplish. Another, possibly a little cleaner, approach would be to use an Resolver. The resolver would take care of loading the data until a route eventually gets activated. Your AppComponent could then take care of showing/hiding a splash-screen, as long as routing is in progress (Check this SO question to see how to react on navigation start/end events).

A guard would then only be needed to block/allow access to a route based on other conditions. Hope this helps and gives you a second thought on possible implementation ideas.