Some new features have been added to the Angular Router with version 7.1.0:

  • Router guards can now return a UrlTree. This allows a guard to cancel the current navigation and redirect to the URL represented by the UrlTree.
  • There is now a notion of “guard priority”, which is used as a tie-breaker when multiple guards return a UrlTree during a single navigation.
  • A new configuration option for runGuardsAndResolvers called pathParamsChange has been added.

In this article, we’ll explore how to start using these new features. We’ll also understand the motivations behind them, and take a glance at how they are implemented in the Angular sources.

Jason Aden covered many of these topics in his excellent talk at AngularConnect 2018, so please be sure to check that out as well. The router portion of his talk starts here.

Perform Navigation/Redirects from Guards by returning a UrlTree

#26478, #26521

Motivation

Prior to this change, when multiple guards were executed during navigation, it was possible for each of them to start a navigation by calling navigateByUrl. In such scenarios, it was unclear as to which guard’s navigation should win.

With this change, the guard with the highest priority that returns a UrlTreewill cancel the current navigation, and a redirect will be made to the URL represented by the returned UrlTree.
We’ll see what “highest priority” means shortly.

Background

Before digging into this change, it’s worth understanding the differences between a URL and a UrlTree.

Inside the Angular Router, a URL string is represented using a structure called a UrlTree. It’s easy to convert a string URL into a UrlTree using the parseUrl method of the Router service:

const url = 'target';
const tree: UrlTree = this.router.parseUrl(url);
UrlTree generated from the URL ‘target’

If you’d like to know more about the relationship between URLs and UrlTrees in Angular, I’ve written about them in-depth in this article.

The Change

Prior to this change, Router guards could only return boolean values. A return value of true meant the navigation could proceed, and a return value of false would cancel the navigation.

Now, for CanActivate, CanActivateChild, and CanDeactivate, there is a third option; these guards can return a UrlTree directly. In these scenarios, the current navigation is cancelled, and a new navigation is created, which routes to the path specified by the returned UrlTree. For example, if a user fails an authentication guard, you may want to redirect that user directly to the login page.

Example

This example application has two routes:

const ROUTES: Route[] = [
  { path: 'target', component: TargetComponent },
  { path: 'redir', component: NeverGetHereComponent, canActivate: [CanActivateRouteGuard] }
];

We’ll make it so that /redir always redirects to /target instead of displaying NeverGetHereComponent. Rather than using redirectTo: 'target'inside of the Route definition for path: 'redir', we’ll let the CanActivateRouteGuard initiate the redirect instead.

We’ll start with an ordinary guard function, and modify its canActivatemethod to return a UrlTree instead of a Boolean value:

import { Injectable } from '@angular/core';
import { CanActivate,
         ActivatedRouteSnapshot,
         RouterStateSnapshot,
         Router,
         UrlTree } from '@angular/router';

@Injectable()
export class CanActivateRouteGuard implements CanActivate {

  constructor(private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree {
    const url = 'target';
    const tree: UrlTree = this.router.parseUrl(url);
    return tree;
  }
}
On line 15, we create a UrlTree from the URL string ‘target’.

As previously mentioned, parseUrl is a function from the Router service. It takes a URL encoded as a string and transforms it into a UrlTree.

Our guard is ready, so we’ll attach it to the { path: 'redir' ... } route.

{ path: 'redir',
  component: NeverGetHereComponent,
  canActivate:[CanActivateRouteGuard] }

Now, whenever we route to /redir, the navigation will proceed as normal until it checks the guards. When our guard function returns a UrlTree pointing to target, the navigation to redir will be cancelled, and a new navigation will begin, targeting target. We can confirm this by clicking the redirect link in Stackblitz:

and then checking the browser console:

Bingo! NavigationCancel with “NavigationCancellingError: Redirecting to ‘/target’ ” message, followed by a NavigationStart to ‘/target’

That’s all it takes to set up redirects from route guards!
Guard Priority
We frequently have multiple guards protecting a route. You may be wondering what happens when multiple guards try to perform a redirect during a navigation. What if several of these guards return UrlTrees?
With Angular 7.1.0, there is a notion of guard priority. Guard priority is implemented internally via a custom RxJS operator called prioritizedGuardValue. We won’t get into the implementation details here, but this operator will make sure that the guard with the highest priority wins whenever multiple guards return UrlTrees.
Consider the following example:

{ path: 'redir', 
  canActivate: [CanActivateRouteGuard, CanActivateRouteGuard2], 
  children: [{
    path: 'dir', 
    component: NeverGetHereComponent, 
    canActivate: [ChildCanActivateRouteGuard]
    }]
}
parent and child canActivate guards

Here is how guard priority is calculated for multiple canActivate guards:

  • The current route's canActivate guards are processed before any of its children’s canActivate guards.
  • Within a canActivate array, the guard at index 0 has the highest priority, followed by the guard at index 1, and so on.

Another way of thinking about canActivate is that the guard closest to the root of the application has the highest priority. So in the above snippet, CanActivateRouteGuard has the highest priority, followed by CanActivateRouteGuard2, and then ChildCanActivateRouteGuard.

All guards in a given canActivate array are executed in parallel, but the router will wait until any guards with a higher priority to finish before moving on. So in the above example:

  • Even if CanActivateRouteGuard2 returns a UrlTree immediately:
    the router will still wait for CanActivateRouteGuard to resolve before initiating a new navigation.
  • If CanActivateRouteGuard returns a UrlTree:
    that will win.
  • If it returns false:
    the entire navigation fails (and no redirects happen).
  • If it simply returns true:
    then the UrlTree returned by CanActivateRouteGuard2 will be navigated to.

You can see how different scenarios play out by looking at the tests for the prioritizedGuardValue operator.

You can also experiment at this stackblitz. I recommend going into the three guards, and changing their delays and return values, as well as their order in the canActivate array in app.module.ts.

runGuardsAndResolvers has a new option: pathParamsChange

#26861

The official documentation describes the runGuardsAndResolvers option as:

defines when guards and resolvers will be run. By default they run only when the matrix parameters of the route change. When set to paramsOrQueryParamsChange they will also run when query params change. And when set to always, they will run every time.

With the new pathParamsChange option, guards and resolver functions will run only if the path or path parameters change. Changes to any other types of parameters, such as matrix parameters or query parameters, will not cause the guards and resolvers to run.

Motivation

As is described in the sources:

`pathParamsChange` Run guards and resolvers path or any path params change. This mode is * useful if you want to ignore changes to all optional parameters such as query *and* matrix * params.

Usage example

runGuardsAndResolvers is a property of Routes, so it is used in a route configuration:

{ path: '...', runGuardsAndResolvers: 'pathParamsChange'}

Summary

With the release of Angular 7.1.0, Route Guard functions now have the option of returning a UrlTree to cancel the current navigation and redirect to a route.

To initiate a redirect from a guard function, do the following:

  • Create a guard function which uses parseUrl to return a UrlTree
  • Register the guard with a route, as normal

There is also a new option for specifying when to run resolvers and guard functions, called pathParamsChange, which is useful when you only want to run guards and resolvers on essential navigations and ignore any optional parameters, like matrix or query params.