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.
Advertisement
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.