Execute Angular Guards Sequentially and in Order
TL;DR
Wiki topics:
🌐 · Web Development
Execute Angular Guards Sequentially and in Order

TL;DR
By default, Angular runs all guards for a route in parallel, stopping at the first blocking result. This utility wraps that behavior to force sequential execution in the order you define: continue as long as each guard returns true, and stop immediately if a guard returns false or an UrlTree (redirect).
canActivate: [
OrderedAsyncGuardUtils.orderedAsyncGuards([AuthGuard, RoleGuard, BusinessGuard])
]
The Problem
In complex apps, you often need to control the execution order of guards to:
- Ensure prerequisites (e.g., authentication before role checks),
- Avoid unnecessary calls (don’t hit APIs if a previous guard already blocks),
- Make navigation logic predictable and readable.
Angular runs guards in parallel by default. Even though it stops at the first blocking result, the order is not guaranteed, and multiple guards may run simultaneously.
The Solution
OrderedAsyncGuardUtils lets you:
- Instantiate guards via Angular DI (
inject(...)), - Normalize return types (
boolean | UrlTree | Promise | Observable) intoObservable<boolean | UrlTree>, - Chain them sequentially using RxJS (
concatMap), - Stop immediately on
falseorUrlTree, - Return the last emitted value (
true,false, orUrlTree).
API Overview
Guard Interface
export interface OrderedGuard {
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree>;
}
Guard Class Type
export type GuardClass = { new (...args: any[]): OrderedGuard };
Utility Method
OrderedAsyncGuardUtils.orderedAsyncGuards(guards: GuardClass[]): CanActivateFn;
Example Usage
1) Three simple guards
@Injectable({ providedIn: 'root' })
export class AuthGuard implements OrderedGuard {
constructor(private auth: AuthService) {}
canActivate(): boolean | UrlTree {
return this.auth.isLoggedIn() ? true : this.auth.redirectToLoginTree();
}
}
@Injectable({ providedIn: 'root' })
export class RoleGuard implements OrderedGuard {
constructor(private roles: RoleService) {}
canActivate(route: ActivatedRouteSnapshot): boolean {
return this.roles.hasRequiredRoles(route.data['roles']);
}
}
@Injectable({ providedIn: 'root' })
export class BusinessGuard implements OrderedGuard {
constructor(private business: BusinessService) {}
async canActivate(): Promise<boolean | UrlTree> {
const ok = await this.business.checkPrerequisites();
return ok ? true : this.business.redirectToPrereqTree();
}
}
2) Route configuration with ordered guards
{
path: 'secure-area',
component: SecureComponent,
data: { roles: ['ADMIN', 'EDITOR'] },
canActivate: [
OrderedAsyncGuardUtils.orderedAsyncGuards([AuthGuard, RoleGuard, BusinessGuard])
]
}
Result:
AuthGuardruns first.- If
true,RoleGuardruns next. - If
true,BusinessGuardruns last. - If any guard returns
falseorUrlTree, execution stops and that value is returned.
Core Utility Code
function toObservable(
value: boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree>
): Observable<boolean | UrlTree> {
if (isObservable(value)) return value as Observable<boolean | UrlTree>;
if (value instanceof Promise) return from(value);
return of(value);
}
export class OrderedAsyncGuardUtils {
static orderedAsyncGuards(guards: GuardClass[]): CanActivateFn {
return (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> => {
const instances = guards.map(g => inject(g)) as unknown as OrderedGuard[];
return from(instances).pipe(
concatMap(guard => toObservable(guard.canActivate(route, state))),
takeWhile(result => result === true, true),
last()
);
};
}
} 메타데이터
- post_id
- 9fa5367fa848
- slug
- execute-angular-guards-sequentially-and-in-order-9fa5367fa848
- url
- https://medium.com/@anassguendef/execute-angular-guards-sequentially-and-in-order-9fa5367fa848
- canonical_url
- https://medium.com/@anassguendef/execute-angular-guards-sequentially-and-in-order-9fa5367fa848
- author_url
- https://medium.com/@anassguendef
- status
- ok
- fetched_at
- 2026-08-10 19:13:05