Angular 라우팅 시스템에서 여러 개의 가드(Guard)를 배열로 지정하면, 기본적으로 모든 가드가 동시에(병렬로) 실행됩니다. 예를 들어, 다음과 같이 라우트 설정을 구성했다고 가정해 봅시다.
{
path: 'dashboard',
canActivate: [AuthenticationGuard, AuthorizationGuard],
}
이 배열의 순서와 상관없이 AuthenticationGuard의 결과가 false로 반환되더라도 AuthorizationGuard는 여전히 실행됩니다. 하지만 실제 비즈니스 로직에서는 첫 번째 가드(예: 인증 확인)가 성공했을 때만 두 번째 가드(예: 권한 확인 및 API 호출)가 실행되기를 원합니다.
함수형 가드(Functional Guards)와 inject 함수의 활용
과거에는 이러한 순차적 실행을 구현하기 위해 여러 가드를 내부에서 순서대로 호출하는 래퍼(Wrapper) 클래스를 별도로 작성해야 했습니다. 하지만 Angular 15부터 도입된 함수형 라우트 가드(Functional Router Guards)와 inject 함수를 활용하면 훨씬 간결하고 유연하게 이를 해결할 수 있습니다.
클래스 기반 가드를 함수형으로 변환하면 다음과 같이 작성할 수 있습니다.
canActivate: [(route, state) => inject(AuthenticationGuard).canActivate(route, state)]
이러한 함수형 접근 방식은 가드 로직을 결합하고 순서를 제어하는 데 큰 이점을 제공합니다.
동기 가드의 순차적 실행 구현
모든 가드가 동기적으로 실행되어 즉시 불리언(Boolean) 값을 반환한다고 가정할 때, 순차적 실행을 위한 고차 함수는 다음과 같이 설계할 수 있습니다.
const runSyncGuardsInOrder = (guards: any[]) => {
return (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean => {
return guards.every((GuardClass) => {
const instance = inject(GuardClass);
return instance.canActivate(route, state) === true;
});
};
};
const APP_ROUTES: Routes = [
{
path: 'settings',
canActivate: [runSyncGuardsInOrder([AuthenticationGuard, AuthorizationGuard])],
}
];
Array.prototype.every 메서드는 첫 번째 false를 만나는 즉시 순회를 중단하므로, 앞선 가드가 실패하면 후속 가드는 실행되지 않습니다.
비동기 가드의 순차적 실행 및 RxJS 활용
비동기 가드를 순차적으로 실행하면 네트워크 요청이 직렬화되어 페이지 로딩 속도가 저하될 수 있다는 점에 유의해야 합니다. 성능이 중요한 환경에서는 병렬 실행 후 결과를 취합하는 방식을 고려하는 것이 좋습니다.
모든 가드가 Observable<boolean | UrlTree>를 반환하고 즉시 완료된다고 가정할 때, RxJS 연산자를 활용하여 비동기 순차 실행 로직을 구성할 수 있습니다.
type AsyncGuardClass = new (...args: any[]) => {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree>;
};
const runAsyncGuardsInOrder = (guards: AsyncGuardClass[]): CanActivateFn => {
return (route, state) => {
const guardInstances = guards.map((cls) => inject(cls));
return from(guardInstances).pipe(
concatMap((instance) => instance.canActivate(route, state)),
takeWhile((result) => result === true, true),
last()
);
};
};
const APP_ROUTES: Routes = [
{
path: 'admin',
canActivate: [runAsyncGuardsInOrder([AuthenticationGuard, AdminRoleGuard])],
}
];
이 구현체에서 concatMap은 각 가드의 Observable이 완료될 때까지 기다린 후 다음 가드를 실행합니다. takeWhile 연산자는 true가 아닌 값(예: false 또는 UrlTree)이 방출되면 스트림을 종료하되, inclusive 옵션을 통해 해당 값 자체는 방출하도록 설정했습니다. 마지막으로 last 연산자를 통해 최종 결과값만을 라우터에 전달합니다.