From 8a56c99f874b450bfd59901f5980690e4a0e5ab3 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Wed, 13 May 2020 14:13:43 -0700 Subject: [PATCH] docs(router): Update router guide to use UrlTree for guard redirects (#37100) The current implementation for redirecting users inside guards was in place before the feature was added to allow `CanActivate` and `CanActivateChild` guards to return `UrlTree` for redirecting users. Returning `UrlTree` should be the default method, as it provides a more desirable redirecting experience. When using `router.navigate` followed by `return false`, the `Router` calls `resetUrlToCurrentUrlTree` (in the `finalize` operator) before processing the navigation to the new route. This can result in an undesirable history if the navigation was the first navigation in the application - that is, the route will briefly be reset to just `/` (see #36187). Fixes #36187 PR Close #37100 --- .../examples/router/src/app/auth/auth.guard.2.ts | 11 +++++------ .../examples/router/src/app/auth/auth.guard.3.ts | 14 +++++++------- .../examples/router/src/app/auth/auth.guard.4.ts | 14 +++++++------- aio/content/guide/router.md | 6 +++--- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/aio/content/examples/router/src/app/auth/auth.guard.2.ts b/aio/content/examples/router/src/app/auth/auth.guard.2.ts index 2ce03c8c9f..1b813fc5c2 100644 --- a/aio/content/examples/router/src/app/auth/auth.guard.2.ts +++ b/aio/content/examples/router/src/app/auth/auth.guard.2.ts @@ -1,6 +1,6 @@ // #docregion import { Injectable } from '@angular/core'; -import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; +import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, UrlTree } from '@angular/router'; import { AuthService } from './auth.service'; @@ -12,21 +12,20 @@ export class AuthGuard implements CanActivate { canActivate( next: ActivatedRouteSnapshot, - state: RouterStateSnapshot): boolean { + state: RouterStateSnapshot): true|UrlTree { let url: string = state.url; return this.checkLogin(url); } - checkLogin(url: string): boolean { + checkLogin(url: string): true|UrlTree { if (this.authService.isLoggedIn) { return true; } // Store the attempted URL for redirecting this.authService.redirectUrl = url; - // Navigate to the login page with extras - this.router.navigate(['/login']); - return false; + // Redirect to the login page + return this.router.parseUrl('/login'); } } // #enddocregion diff --git a/aio/content/examples/router/src/app/auth/auth.guard.3.ts b/aio/content/examples/router/src/app/auth/auth.guard.3.ts index 97dc4546b4..5b958074f9 100644 --- a/aio/content/examples/router/src/app/auth/auth.guard.3.ts +++ b/aio/content/examples/router/src/app/auth/auth.guard.3.ts @@ -4,7 +4,8 @@ import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot, - CanActivateChild + CanActivateChild, + UrlTree } from '@angular/router'; import { AuthService } from './auth.service'; @@ -16,7 +17,7 @@ export class AuthGuard implements CanActivate, CanActivateChild { canActivate( route: ActivatedRouteSnapshot, - state: RouterStateSnapshot): boolean { + state: RouterStateSnapshot): true|UrlTree { let url: string = state.url; return this.checkLogin(url); @@ -24,20 +25,19 @@ export class AuthGuard implements CanActivate, CanActivateChild { canActivateChild( route: ActivatedRouteSnapshot, - state: RouterStateSnapshot): boolean { + state: RouterStateSnapshot): true|UrlTree { return this.canActivate(route, state); } // #enddocregion can-activate-child - checkLogin(url: string): boolean { + checkLogin(url: string): true|UrlTree { if (this.authService.isLoggedIn) { return true; } // Store the attempted URL for redirecting this.authService.redirectUrl = url; - // Navigate to the login page - this.router.navigate(['/login']); - return false; + // Redirect to the login page + return this.router.parseUrl('/login'); } // #docregion can-activate-child } diff --git a/aio/content/examples/router/src/app/auth/auth.guard.4.ts b/aio/content/examples/router/src/app/auth/auth.guard.4.ts index feca8d2eb0..13c8f9dff1 100644 --- a/aio/content/examples/router/src/app/auth/auth.guard.4.ts +++ b/aio/content/examples/router/src/app/auth/auth.guard.4.ts @@ -6,7 +6,8 @@ import { ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild, - NavigationExtras + NavigationExtras, + UrlTree } from '@angular/router'; import { AuthService } from './auth.service'; @@ -16,17 +17,17 @@ import { AuthService } from './auth.service'; export class AuthGuard implements CanActivate, CanActivateChild { constructor(private authService: AuthService, private router: Router) {} - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): true|UrlTree { let url: string = state.url; return this.checkLogin(url); } - canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): true|UrlTree { return this.canActivate(route, state); } - checkLogin(url: string): boolean { + checkLogin(url: string): true|UrlTree { if (this.authService.isLoggedIn) { return true; } // Store the attempted URL for redirecting @@ -42,8 +43,7 @@ export class AuthGuard implements CanActivate, CanActivateChild { fragment: 'anchor' }; - // Navigate to the login page with extras - this.router.navigate(['/login'], navigationExtras); - return false; + // Redirect to the login page with extras + return this.router.createUrlTree(['/login'], navigationExtras); } } diff --git a/aio/content/guide/router.md b/aio/content/guide/router.md index 1755434252..72bbbb15ef 100644 --- a/aio/content/guide/router.md +++ b/aio/content/guide/router.md @@ -2737,7 +2737,7 @@ If the user is logged in, it returns true and the navigation continues. The `ActivatedRouteSnapshot` contains the _future_ route that will be activated and the `RouterStateSnapshot` contains the _future_ `RouterState` of the application, should you pass through the guard check. If the user is not logged in, you store the attempted URL the user came from using the `RouterStateSnapshot.url` and tell the router to redirect to a login page—a page you haven't created yet. -This secondary navigation automatically cancels the current navigation; `checkLogin()` returns `false`. +Returning a `UrlTree` tells the `Router` to cancel the current navigation and schedule a new one to redirect the user. {@a add-login-component} @@ -2790,8 +2790,8 @@ Extend the `AuthGuard` to protect when navigating between the `admin` routes. Open `auth.guard.ts` and add the `CanActivateChild` interface to the imported tokens from the router package. Next, implement the `canActivateChild()` method which takes the same arguments as the `canActivate()` method: an `ActivatedRouteSnapshot` and `RouterStateSnapshot`. -The `canActivateChild()` method can return an `Observable` or `Promise` for async checks and a `boolean` for sync checks. -This one returns a `boolean`: +The `canActivateChild()` method can return an `Observable` or `Promise` for async checks and a `boolean` or `UrlTree` for sync checks. +This one returns either `true` to allow the user to access the admin feature module or `UrlTree` to redirect the user to the login page instead: