Reputation: 1353
I may have misunderstood how canActivateChild
works in Angular 11's routing.
With a route definition like the below one and canActivateChild
returning false
, ParentComponent
isn't constructed but I'm expecting to have ParentComponent
rendered while the child component not initialized and shown.
Route definition:
{
path: "parent",
component: ParentComponent,
canActivate: ["canActivate"],
canActivateChild: ["canActivateChild"],
children: [
{
path: "child",
component: ChildComponent
}
]
}
Here's a stackblitz url to a full working example to demonstrate what I'm asking for.
Is this an expected behavior? Must I change my route definition to the following in order to have ParentComponent
showing?
{
path: "parent",
component: ParentComponent,
canActivate: ["canActivate"],
children: [
{
path: "",
canActivateChild: ["canActivateChild"],
children: [
{
path: "child",
component: ChildComponent
}
]
}
]
}
Upvotes: 4
Views: 1505
Reputation: 11979
The canActivateChild
guards are run before canActivate
guards and if at least one returns false
, the canActivate
ones won't be run anymore and the current navigation will be cancelled. In case they are helpful, here's a link to my notes on Angular Router, after spending some time reading its source code.
Whenever a navigation is about to take place, before it completes, it must go through some phases. These are all described here, in a big RxJS stream.
One phase of this entire process is the guards checking.
The checkGuards
function is defined here, as follows:
/* ... */
return runCanDeactivateChecks(
canDeactivateChecks, targetSnapshot!, currentSnapshot, moduleInjector)
.pipe(
mergeMap(canDeactivate => {
return canDeactivate && isBoolean(canDeactivate) ?
runCanActivateChecks(
targetSnapshot!, canActivateChecks, moduleInjector, forwardEvent) :
of(canDeactivate);
}),
map(guardsResult => ({...t, guardsResult})));
});
/* ... */
So, as probably expected, it will run canDeactivate
guards and if one of them fails, then the navigation will be cancelled. But, if everything is alright with canDeactivate
guards, then canActivate
and canActivateChild
guards can be run.
If you scroll down a bit in the check_guards.ts
file, you should find the runCanActivateChecks
function:
function runCanActivateChecks(
futureSnapshot: RouterStateSnapshot, checks: CanActivate[], moduleInjector: Injector,
forwardEvent?: (evt: Event) => void) {
return from(checks).pipe(
concatMap((check: CanActivate) => {
return from([
fireChildActivationStart(check.route.parent, forwardEvent),
fireActivationStart(check.route, forwardEvent),
// !
runCanActivateChild(futureSnapshot, check.path, moduleInjector),
runCanActivate(futureSnapshot, check.route, moduleInjector)
])
.pipe(concatAll(), first(result => {
return result !== true;
}, true as boolean | UrlTree));
}),
first(result => {
return result !== true;
}, true as boolean | UrlTree));
}
A few interesting things happen here. First, from(arrayOfObservables)
will emit a next
notification that consits of arrayOfObservables
. The first 2 observables of this array(fire*
) are not very important right now and they simply return of(true)
. Then, we have runCanActivateChild
and, for now, it's important that its return type is an Observable<boolean|UrlTree>
. If we look below, we have concatAll()
, which subscribes to each observable and queues the new ones that arrive until the current inner observable completes.
Then, first(...)
makes sure that if we encounter any value which is not true
, then we should stop.
Let's take your example. If a canActivateChild
guard returns false, then runCanActivate
won't be run, and the guardResult
value(2 snippets above) will be false
. And if that happens, then the navigation will be cancelled;
filter(t => {
if (!t.guardsResult) {
this.resetUrlToCurrentUrlTree();
const navCancel =
new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), '');
eventsSubject.next(navCancel);
t.resolve(false);
return false;
}
return true;
}),
So, this is why your example is not working as expected. If you'd like to read more about the inner workings of Angular Router, I'd recommend checking out these articles:
Upvotes: 2