Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(router): Add CanMatch guard to control whether a Route should ma…
…tch (#46021) Currently we have two main types of guards: `CanLoad`: decides if we can load a module (used with lazy loading) `CanActivate` and friends. It decides if we can activate/deactivate a route. So we always decide where we want to navigate first ("recognize") and create a new router state snapshot. And only then we run guards to check if the navigation should be allowed. This doesn't handle one very important use case where we want to decide where to navigate based on some data (e.g., who the user is). I suggest to add a new guard that allows us to do that. ``` [ {path: 'home', component: AdminHomePage, canUse: [IsAdmin]}, {path: 'home', component: SimpleHomePage} ] ``` Here, navigating to '/home' will render `AdminHomePage` if the user is an admin and will render 'SimpleHomePage' otherwise. Note that the url will remain '/home'. With the introduction of standalone components and new features in the Router such as `loadComponent`, there's a case for deprecating `CanLoad` and replacing it with the `CanMatch` guard. There are a few reasons for this: * One of the intentions of having separate providers on a Route is that lazy loading should not be an architectural feature of an application. It's an optimization you do for code size. That is, there should not be an architectural feature in the router to specifically control whether to lazy load something or not based on conditions such as authentication. This is a slight nuanced difference between the proposed canUse guard: this guard would control whether you can use the route at all and as a side-effect, whether we download the code. `CanLoad` only specified whether the code should be downloaded so canUse is more powerful and more appropriate. * The naming of `CanLoad` will be potentially misunderstood for the `loadComponent` feature. Because it applies to `loadChildren`, it feels reasonable to think that it will also apply to `loadComponent`. This isn’t the case: since we don't need to load the component until right before activation, we defer the loading until all guards/resolvers have run. When considering the removal of `CanLoad` and replacing it with `CanMatch`, this does inform another decision that needed to be made: whether it makes sense for `CanMatch` guards to return a UrlTree or if they should be restricted to just boolean. The original thought was that no, these new guards should not allow returning UrlTree because that significantly expands the intent of the feature from simply “can I use the route” to “can I use this route, and if not, should I redirect?” I now believe it should allowed to return `UrlTree` for several reasons: * For feature parity with `CanLoad` * Because whether we allow it as a return value or not, developers will still be able to trigger a redirect from the guards using the `Router.navigate` function. * Inevitably, there will be developers who disagree with the philosophical decision to disallow `UrlTree` and we don’t necessarily have a compelling reason to refuse this as a feature. Relates to #16211 - `CanMatch` instead of `CanActivate` would prevent blank screen. Additional work is required to close this issue. This can be accomplished by making the initial navigation result trackable (including the redirects). Resolves #14515 Replaces #16416 Resolves #34231 Resolves #17145 Resolves #12088 PR Close #46021
- Loading branch information
1 parent
96f5c97
commit de058bb
Showing
16 changed files
with
645 additions
and
329 deletions.
There are no files selected for viewing
68 changes: 32 additions & 36 deletions
68
aio/content/examples/router/src/app/admin/admin-routing.module.2.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,40 @@ | ||
// #docplaster | ||
// #docregion | ||
import { NgModule } from '@angular/core'; | ||
import { RouterModule, Routes } from '@angular/router'; | ||
|
||
import { AdminComponent } from './admin/admin.component'; | ||
import { AdminDashboardComponent } from './admin-dashboard/admin-dashboard.component'; | ||
import { ManageCrisesComponent } from './manage-crises/manage-crises.component'; | ||
import { ManageHeroesComponent } from './manage-heroes/manage-heroes.component'; | ||
import {NgModule} from '@angular/core'; | ||
import {RouterModule, Routes} from '@angular/router'; | ||
|
||
// #docregion admin-route | ||
import { AuthGuard } from '../auth/auth.guard'; | ||
import {AuthGuard} from '../auth/auth.guard'; | ||
|
||
import {AdminDashboardComponent} from './admin-dashboard/admin-dashboard.component'; | ||
import {AdminComponent} from './admin/admin.component'; | ||
import {ManageCrisesComponent} from './manage-crises/manage-crises.component'; | ||
import {ManageHeroesComponent} from './manage-heroes/manage-heroes.component'; | ||
|
||
const adminRoutes: Routes = [{ | ||
path: 'admin', | ||
component: AdminComponent, | ||
canActivate: [AuthGuard], | ||
|
||
const adminRoutes: Routes = [ | ||
{ | ||
path: 'admin', | ||
component: AdminComponent, | ||
canActivate: [AuthGuard], | ||
// #enddocregion admin-route | ||
// #docregion can-match | ||
canMatch: [AuthGuard], | ||
// #enddocregion can-match | ||
// #docregion admin-route | ||
children: [{ | ||
path: '', | ||
children: [ | ||
{ | ||
path: '', | ||
children: [ | ||
{ path: 'crises', component: ManageCrisesComponent }, | ||
{ path: 'heroes', component: ManageHeroesComponent }, | ||
{ path: '', component: AdminDashboardComponent } | ||
], | ||
// #enddocregion admin-route | ||
canActivateChild: [AuthGuard] | ||
// #docregion admin-route | ||
} | ||
] | ||
} | ||
]; | ||
{path: 'crises', component: ManageCrisesComponent}, | ||
{path: 'heroes', component: ManageHeroesComponent}, | ||
{path: '', component: AdminDashboardComponent} | ||
], | ||
// #enddocregion admin-route | ||
canActivateChild: [AuthGuard] | ||
// #docregion admin-route | ||
}] | ||
}]; | ||
|
||
@NgModule({ | ||
imports: [ | ||
RouterModule.forChild(adminRoutes) | ||
], | ||
exports: [ | ||
RouterModule | ||
] | ||
}) | ||
export class AdminRoutingModule {} | ||
@NgModule({imports: [RouterModule.forChild(adminRoutes)], exports: [RouterModule]}) | ||
export class AdminRoutingModule { | ||
} | ||
// #enddocregion |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.