I did not find a standard solution, and those offered on the Internet are limited to one page. Therefore, I wrote my own and decided to share it.
Case description
Application pages are divided into 3 groups:
- For authorized users only
- For unauthorized users only
- For any users
You can log in or log out on any page.
If you enter / exit on a page with limited access, then you need to go to the allowed page.
If the page has no restrictions, then you need to stay on the current page.
For a better understanding, it is advisable to know about:
Decision
Differentiation of access rights to pages is carried out through guard - CanActivate. The check is carried out when you go to the page, but does not react to changes in rights when the transition has already been made. To avoid duplication of logic for access rights, we forcibly restart the guards.
Restarting guards for the current page is done by navigating the current url. And changes to the strategies Router.onSameUrlNavigation and Route.runGuardsAndResolvers.
Here's a ready-made solution. More details in the next section.
import { Injectable } from '@angular/core';
import { ActivatedRoute, PRIMARY_OUTLET, Router, RunGuardsAndResolvers } from '@angular/router';
@Injectable()
export class GuardControlService {
constructor(
private route: ActivatedRoute,
private router: Router,
) {}
/**
* guard- url
*/
forceRunCurrentGuards(): void {
// Router.onSameUrlNavigation url
const restoreSameUrl = this.changeSameUrlStrategy(this.router, 'reload');
// ActivatedRoute primary outlet
const primaryRoute: ActivatedRoute = this.getLastRouteForOutlet(this.route.root, PRIMARY_OUTLET);
// runGuardsAndResolvers ActivatedRoute url
const restoreRunGuards = this.changeRunGuardStrategies(primaryRoute, 'always');
//
this.router.navigateByUrl(
this.router.url
).then(() => {
// onSameUrlNavigation
restoreSameUrl();
// runGuardsAndResolvers
restoreRunGuards();
});
}
/**
* onSameUrlNavigation
* @param router - Router,
* @param strategy -
* @return callback
*/
private changeSameUrlStrategy(router: Router, strategy: 'reload' | 'ignore'): () => void {
const onSameUrlNavigation = router.onSameUrlNavigation;
router.onSameUrlNavigation = strategy;
return () => {
router.onSameUrlNavigation = onSameUrlNavigation;
}
}
/**
* route outlet-
* @param route - Route
* @param outlet - outlet-,
* @return ActivatedRoute outlet
*/
private getLastRouteForOutlet(route: ActivatedRoute, outlet: string): ActivatedRoute {
if (route.children?.length) {
return this.getLastRouteForOutlet(
route.children.find(item => item.outlet === outlet),
outlet
);
} else {
return route;
}
}
/**
* runGuardsAndResolvers ActivatedRoute ,
* @param route - ActivatedRoute
* @param strategy -
* @return callback
*/
private changeRunGuardStrategies(route: ActivatedRoute, strategy: RunGuardsAndResolvers): () => void {
const routeConfigs = route.pathFromRoot
.map(item => {
if (item.routeConfig) {
const runGuardsAndResolvers = item.routeConfig.runGuardsAndResolvers;
item.routeConfig.runGuardsAndResolvers = strategy;
return runGuardsAndResolvers;
} else {
return null;
}
});
return () => {
route.pathFromRoot
.forEach((item, index) => {
if (item.routeConfig) {
item.routeConfig.runGuardsAndResolvers = routeConfigs[index];
}
});
}
}
}
Additional solution description
The first thing you want to try to restart the guards is to use navigation to the current url.
this.router.navigateByUrl(this.router.url);
But, by default, the current url transition event is ignored and nothing happens. For this to work, you need to configure the routing.
Setting up routing
1. Change the Router strategy. onSameUrlNavigation
onSameUrlNavigation can take the following values:
onSameUrlNavigation: 'reload' | 'ignore';
For sensitivity to the transition to the current url, you need to set 'reload'.
The policy change does not reload, but generates an additional navigation event. It can be obtained through a subscription:
this.router.events.subscribe();
2. Change the Route strategy. runGuardsAndResolvers
runGuardsAndResolvers can take the following values:
type RunGuardsAndResolvers = 'pathParamsChange' | 'pathParamsOrQueryParamsChange' | 'paramsChange' | 'paramsOrQueryParamsChange' | 'always' | ((from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean);
For sensitivity to the transition to the current url, you need to set 'always'.
Configuring Routing During Application Configuration
onSameUrlNavigation:
const routes: : Route[] = [];
@NgModule({
imports: [
RouterModule.forRoot(
routes,
{ onSameUrlNavigation: 'reload' }
)
]
})
runGuardsAndResolvers:
const routes: Route[] = [
{
path: '',
component: AppComponent,
runGuardsAndResolvers: 'always',
}
];
Configuring Routing at Runtime
constructor(
private router: Router,
private route: ActivatedRoute
) {
this.router.onSameUrlNavigation = 'reload';
this.route.routeConfig.runGuardsAndResolvers = 'always';
}
Restarting guards
To restart the guards of one specific page, it is enough to configure routing during configuration.
But to reload the guards of any page, changing runGuardsAndResolvers in each Route will lead to unnecessary checks. And the need to always remember this parameter - to errors.
Since our case assumes a restart for any page without restrictions in setting up the application, you need:
1. Replace onSameUrlNavigation and keep the current value
// Router.onSameUrlNavigation url
const restoreSameUrl = this.changeSameUrlStrategy(this.router, 'reload');
...
/**
* onSameUrlNavigation
* @param router - Router,
* @param strategy -
* @return callback
*/
private changeSameUrlStrategy(router: Router, strategy: 'reload' | 'ignore'): () => void {
const onSameUrlNavigation = router.onSameUrlNavigation;
router.onSameUrlNavigation = strategy;
return () => {
router.onSameUrlNavigation = onSameUrlNavigation;
}
}
2. Get ActivatedRoute for the current url
Since the inject ActivatedRoute is carried out in the service, the resulting ActivatedRoute is not associated with the current url.
ActivatedRoute for the current url lies in the last primary outlet and you need to find it:
// ActivatedRoute primary outlet
const primaryRoute: ActivatedRoute = this.getLastRouteForOutlet(this.route.root, PRIMARY_OUTLET);
...
/**
* route outlet-
* @param route - Route
* @param outlet - outlet-,
* @return ActivatedRoute outlet
*/
private getLastRouteForOutlet(route: ActivatedRoute, outlet: string): ActivatedRoute {
if (route.children?.length) {
return this.getLastRouteForOutlet(
route.children.find(item => item.outlet === outlet),
outlet
);
} else {
return route;
}
}
3. Replace runGuardsAndResolvers for all ActivatedRoute and its ancestors, keeping the current values
A guard restricting access can be located in any of the ancestors of the current ActivatedRoute. All ancestors are located in pathFromRoot.
// runGuardsAndResolvers ActivatedRoute url
const restoreRunGuards = this.changeRunGuardStrategies(primaryRoute, 'always');
...
/**
* runGuardsAndResolvers ActivatedRoute ,
* @param route - ActivatedRoute
* @param strategy -
* @return callback
*/
private changeRunGuardStrategies(route: ActivatedRoute, strategy: RunGuardsAndResolvers): () => void {
const routeConfigs = route.pathFromRoot
.map(item => {
if (item.routeConfig) {
const runGuardsAndResolvers = item.routeConfig.runGuardsAndResolvers;
item.routeConfig.runGuardsAndResolvers = strategy;
return runGuardsAndResolvers;
} else {
return null;
}
});
return () => {
route.pathFromRoot
.forEach((item, index) => {
if (item.routeConfig) {
item.routeConfig.runGuardsAndResolvers = routeConfigs[index];
}
});
}
}
4. Go to the current url
this.router.navigateByUrl(this.router.url);
5. Return runGuardsAndResolvers and onSameUrlNavigation to their original state
restoreRunGuards();
restoreSameUrl();
6. Combine stages in one function
constructor(
private route: ActivatedRoute,
private router: Router,
) {}
/**
* guard- url
*/
forceRunCurrentGuards(): void {
// Router.onSameUrlNavigation url
const restoreSameUrl = this.changeSameUrlStrategy(this.router, 'reload');
// ActivatedRoute primary outlet
const primaryRoute: ActivatedRoute = this.getLastRouteForOutlet(this.route.root, PRIMARY_OUTLET);
// runGuardsAndResolvers ActivatedRoute url
const restoreRunGuards = this.changeRunGuardStrategies(primaryRoute, 'always');
//
this.router.navigateByUrl(
this.router.url
).then(() => {
// onSameUrlNavigation
restoreSameUrl();
// runGuardsAndResolvers
restoreRunGuards();
});
}
I hope this article was useful to you. If there are other solutions, I will be glad to see them in the comments.