From harness-claude
Protects and preloads Angular routes with functional CanActivateFn, CanDeactivateFn, ResolveFn, and CanMatchFn guards. Covers auth redirects, unsaved-change warnings, data pre-fetching, and lazy module conditional loading.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:angular-routing-guardsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Protect and preload routes with functional CanActivateFn, CanDeactivateFn, ResolveFn, and CanMatchFn guards
Protect and preload routes with functional CanActivateFn, CanDeactivateFn, ResolveFn, and CanMatchFn guards
CanActivateFn)CanDeactivateFn)ResolveFn)CanMatchFn)combineLatest or short-circuit logicCanActivate implementations. Functional guards use inject() directly and are easier to test.true, false, UrlTree, Observable<boolean | UrlTree>, or Promise<boolean | UrlTree> from a guard. Return a UrlTree (via inject(Router).createUrlTree(['/login'])) to redirect instead of just blocking.CanActivateFn for authentication checks. Redirect to the login page and pass the attempted URL as a query param so the login page can redirect back after success.ResolveFn to load required data before the route activates. The resolved data is available in ActivatedRoute.data. This eliminates the need for loading states in the component.CanDeactivateFn to warn users about unsaved changes. The guard receives the component instance — define an interface the component implements (HasUnsavedChanges) and check it in the guard.CanMatchFn instead of CanActivateFn when you want to prevent a lazy module from even loading (not just block navigation to it). This saves bundle bytes for unauthorized users.canActivate array — all must return true for the route to activate.// auth.guard.ts — functional authentication guard
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';
import { map } from 'rxjs';
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
const router = inject(Router);
return auth.isAuthenticated$.pipe(
map((isAuth) =>
isAuth
? true
: router.createUrlTree(['/login'], {
queryParams: { returnUrl: state.url },
})
)
);
};
// role.guard.ts — role-based access
export const adminGuard: CanActivateFn = () => {
const auth = inject(AuthService);
const router = inject(Router);
return auth.hasRole('admin') ? true : router.createUrlTree(['/forbidden']);
};
// unsaved-changes.guard.ts — CanDeactivateFn
export interface HasUnsavedChanges {
hasUnsavedChanges(): boolean;
}
export const unsavedChangesGuard: CanDeactivateFn<HasUnsavedChanges> = (component) => {
if (component.hasUnsavedChanges()) {
return confirm('You have unsaved changes. Leave anyway?');
}
return true;
};
// product.resolver.ts — ResolveFn
import { ResolveFn } from '@angular/router';
import { inject } from '@angular/core';
import { ProductService } from './product.service';
import { Product } from './product.model';
export const productResolver: ResolveFn<Product> = (route) => {
return inject(ProductService).getById(route.paramMap.get('id')!);
};
// Route config
{
path: 'product/:id',
component: ProductDetailComponent,
resolve: { product: productResolver },
canActivate: [authGuard],
}
// Component reads resolved data
export class ProductDetailComponent {
product = inject(ActivatedRoute).snapshot.data['product'] as Product;
}
Functional vs class guards: Class-based guards implementing CanActivate interface are deprecated in Angular 15+. Functional guards have no class overhead, use inject() directly, and are composable as arrays in route config. If you need to wrap a class-based guard for migration, use mapToCanActivate([LegacyGuard]) as a bridge.
CanMatchFn vs CanActivateFn: CanActivate runs after the route is matched but before it renders. CanMatch runs during route matching — if it returns false, Angular continues trying other route alternatives. This means CanMatch can prevent lazy chunks from loading entirely, reducing bandwidth for unauthorized users. It also enables showing different components for the same URL path based on conditions (e.g., A/B testing).
Resolver error handling: If a ResolveFn throws or the observable errors, Angular cancels navigation by default. Add error handling in the resolver or use a catchError to return a fallback:
export const productResolver: ResolveFn<Product | null> = (route) => {
return inject(ProductService)
.getById(route.paramMap.get('id')!)
.pipe(
catchError(() => {
inject(Router).navigate(['/not-found']);
return of(null);
})
);
};
Guard composition: Angular runs canActivate guards in the array order, but all run in parallel by default if they return observables. For serial execution (first guard must pass before second runs), compose with switchMap:
export const composedGuard: CanActivateFn = (route, state) =>
authGuard(route, state).pipe(
switchMap((authed) => (authed === true ? adminGuard(route, state) : of(authed)))
);
Testing functional guards:
TestBed.configureTestingModule({
providers: [{ provide: AuthService, useValue: mockAuthService }, provideRouter([])],
});
const result = TestBed.runInInjectionContext(() => authGuard(mockRoute, mockState));
https://angular.dev/guide/routing/common-router-tasks
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeAngular Router (built-in `@angular/router`) — route configuration for standalone and NgModule projects, functional guards (Angular 14.1+), lazy loading, route resolvers, typed params via signals/observables, programmatic navigation, route data and meta. Use this skill to: - Configure routes (standalone-style or NgModule-style). - Use functional guards (canActivate as function, preferred over class-based in 17+). - Lazy-load components or feature modules. - Implement auth guards via route meta + functional guards. - Read params/queries via `inject(ActivatedRoute)` + signals or RxJS. Do NOT use this skill for: - General conventions (see angular-conventions). - State management (see angular-state-and-rx). - Forms (see angular-forms). - Testing routes (see angular-testing).
Configures routing in Angular v20+ with lazy loading, functional guards, resolvers, and signal-based route parameters. Use for navigation setup, protected routes, and nested routing.
Implements NestJS guards for route protection using @UseGuards, CanActivate, JWT, and role-based access control.