Angular 2 Router Guards order

Viewed 9264

Angular 2 router guards can be defined in an array. for example:

<code>
 canActivate: ['CanAlwaysActivateGuard','AuthGuard']
</code>

following are my questions:

  1. what will be the order of execution for both the guards.
  2. if i want to execute AuthGuard only if CanAlwaysActivateGuard returns true, would that be possible.
5 Answers

I had this problem and came across your post in my search for answers. Finding none -- and having some time today -- I came up with a solution I thought you might be interested in. I created a "CompositeRouteGuard":

import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import {Injectable, Injector} from '@angular/core';
import { Observable } from "rxjs";

@Injectable()
export class CompositeRouteGuard implements CanActivate {

  constructor(  protected router: Router,
                protected injector: Injector ) {
  }

  canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> {
    var compositeCanActivateObservable: Observable<boolean> = Observable.of(true);

    let routeGuards = route.data.routeGuards;

    if(routeGuards){
      for( var i = 0; i < routeGuards.length; i++ ){
        let routeGuard = this.injector.get(routeGuards[i]);
        let canActivateObservable = routeGuard.canActivate(route, state);
        compositeCanActivateObservable = compositeCanActivateObservable.flatMap( (bool) => {
          if(!bool){
            return Observable.of(false);
          }
          else{
            return canActivateObservable;
          }
        });
      }
    }

    return compositeCanActivateObservable;
  }
}

That requires a little extra configuration in your routes.ts. You need to add a "routeGuards" array to the data element of the route.

const routes: Routes = [
  {
    path: '...',
    component: AComponent,
    data: { routeGuards: [ FooRouteGuard, BarRouteGuard, BazRouteGuard ] },
    canActivate: [ CompositeRouteGuard ]
  },
...

I only cared about the canActivate action, but you should be able to easily extend this, to say "canDeactivate" (for example) if you need to.

Now my route guards run "in order" with "and" semantics (all must succeed for route to be activated).

If you nest them, you can decide their order:

import { Routes } from '@angular/router';

const routes: Routes = [
  {
    path: '',
    canActivate: [CanAlwaysActivateGuard],
    children: [
      {
        path: '',
        canActivate: [AuthGuard],
        children: [
          // (...)
        ],
      }
    ],
  }
];

An outer route guard is evaluated before any inner route guards.

You can also inherit the guard run super.canActivate() before the AuthGuard.canActivate().

In this example canActivate() method in both classes returns a Promise.

@Injectable({
  providedIn: 'root',
})
class AuthGuard extends CanAlwaysActivateGuard implements CanActivate {
  constructor(/*...*/) {
    super(/*...*/);
  }

  async canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean | UrlTree> {
    const result = await super.canActivate(next, state);

    if (result === true) { // Check the expected result from the parent
        return await this.logicOfYourAuthGuard(); // Run your logic and return it
    }

    return result; // Return the original result if logic of AuthGuard is not nedded
  }
}

And then, in the route, use only AuthGuard.


Other option is to use canActivateChild, because canActivate guards runs before canActivateChild.

For the current version, you can use child router, for example:

{
    path: 'your-path',
    canActivate: [
        CanAlwaysActivateGuard,
    ],
    canActivateChild: [
        AuthGuard,
    ],
    children: [
    ]
}
Related