I'm using route guards, specifically the canActivate()
method, but Angular is calling ngOnInit()
of my root AppComponent
before canActivate
is called.
I have to wait on some data in canActivate
before the AppComponent
can render it in the template.
How can I do this?
I'm using route guards, specifically the canActivate()
method, but Angular is calling ngOnInit()
of my root AppComponent
before canActivate
is called.
I have to wait on some data in canActivate
before the AppComponent
can render it in the template.
How can I do this?
Share Improve this question edited Jun 7, 2018 at 18:23 jonrsharpe 122k30 gold badges268 silver badges476 bronze badges asked Jun 7, 2018 at 18:22 KingamereKingamere 10.2k24 gold badges78 silver badges115 bronze badges 4-
5
canActivate
gets called to find out if the relevant route ponent loads into the outlet, not the root app ponent. If you need something to happen before the app starts, you'll need to useAPP_INITIALIZER
. – jonrsharpe Commented Jun 7, 2018 at 18:25 -
6
AppComponent
is the default bootstrapped ponent. It does not wait for any routes to activate. – Amit Chigadani Commented Jun 7, 2018 at 18:27 -
You want to make some asynchronous calls inside
canActivate
and activate/don't activate specific ponent based on this async response? – P.S. Commented Jun 7, 2018 at 19:13 - @CommercialSuicide Yes that's right. – Kingamere Commented Jun 7, 2018 at 19:19
2 Answers
Reset to default 3I was dealing with such cases, and here is what I usually do:
1. I create a Resolver service (which implements Resolve
interface). It allows you to get all necessary data before activating the route:
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { DataService } from 'path/to/data.service';
@Injectable()
export class ExampleResolverService implements Resolve<any> {
constructor(private _dataService: DataService) { }
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<any> {
return this._dataService.anyAsyncCall()
.then(response => {
/* Let's imagine, that this method returns response with field "result", which can be equal to "true" or "false" */
/* "setResult" just stores passed argument to "DataService" class property */
this._dataService.setResult(response.result);
})
.catch(err => this._dataService.setResult(false););
}
}
2. Here is how we can deal with AuthGuard, which implements CanActivate
interface:
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { DataService } from 'path/to/data.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private _dataService: DataService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
/* "getResult" method operates with the same class property as setResult, it just returns the value of it */
return this._dataService.getResult(); // will return "true" or "false"
}
}
3. Then you can include the Resolver and the AuthGuard to your routes config, here is just a part (the structure of routes can differ, here is an example with activating the parent ponent):
const routes: Routes = [
{
path: 'app',
ponent: AppComponent,
resolve: {
result: ExampleResolverService // your resolver
},
canActivate: [AuthGuard], // your AuthGuard with "canActivate" method
children: [...] // child routes goes inside the array
}
];
How it works
When you're navigating to /app
, the ExampleResolverService
starts, makes API call and stores the necessary part of response to class property in DataService
via setResult
method (it's the usual setter). Then, when the resolver finished the work, it's time for our AuthGuard
. It gets stored result from DataService
via getResult
method (it's the usual getter), and returns this boolean result (our AuthGuard
expects boolean to be returned, and the route will be activated if it returns true
, and will not be activated if it returns false
);
It's the simplest example without any additional operations with data, the logic is more plex usually, but this skeleton should be enough for basic understanding.
For me, I listened for ROUTE_NAVIGATED events in my app ponent like below
I am using ngrx/router-store to be able to listen to these router actions.
// app.ponent.ts
public ngOnInit(): void {
// grab the action stream
this.actions$.pipe(
// Only pay attention to pleted router
ofType(ROUTER_NAVIGATED),
// Now I can guarantee that my resolve has pleted, as the router has finsihed
// Switch
switchMap(() => {
// Now switch to get the stuff I was waiting for
return this.someService.getStuff();
})
// Need to subscribe to actions as we are in the ponent, not in an effect
// I suppose we should unsubscribe, but the app ponent will never destroy as far as I am aware so will always be listening
).subscribe();