Mahesh
Mahesh

Reputation: 355

Angular 2 Router Guards order

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.

Upvotes: 22

Views: 12190

Answers (6)

Dzinx
Dzinx

Reputation: 57774

  1. The guards will be executed one after the other (left to right). Guards don't wait for the guards that preceed them on the list to return a value. That means, for example:

    • If both your guards are synchronous (return a boolean or a UrlTree), then the first one will execute first, and the second one will also always execute, regardless of whether the first guard returned true or something else.
    • If both your guards fire network requests (i.e. they return a Promise or Observable that doesn't immediately resolve), then both requests will always be fired.
  2. There's no native way to achieve this, but starting from Angular 15, you can write a pretty elegant generic wrapper that functional guards. For example, for synchronous guards it could look like this:

const orderedSyncGuards =
  (guards) =>
    (route, state) =>
      guards.every(guard =>
        inject(guard).canActivate(route, state));

const ROUTE = {
  ...
  canActivate: [orderedSyncGuards([FirstGuard, SecondGuard])]

The code for async guards can be found here.

Upvotes: 0

Lars Gyrup Brink Nielsen
Lars Gyrup Brink Nielsen

Reputation: 4095

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.

Upvotes: 6

hwangzhiming
hwangzhiming

Reputation: 31

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

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

Upvotes: 1

nelson6e65
nelson6e65

Reputation: 1101

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.

Upvotes: 2

Ed MacDonald
Ed MacDonald

Reputation: 156

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).

Upvotes: 11

Max Koretskyi
Max Koretskyi

Reputation: 105459

what will be the order of execution for both the guards.

They will be run synchronously without waiting for each other.

if i want to execute AuthGuard only if CanAlwaysActivateGuard returns true, would that be possible.

No, it's not possible with current implementation. As a workaround you can create a wrapper guards to run your guards in order.

Also see this How to wait for guards in Angular.

Upvotes: 13

Related Questions