Angular Route Guards
A guard can allow navigation or redirect the user to a meaningful alternative. A client-side guard improves navigation flow; it cannot authorize access to protected server data.
How it works
The local desk demo begins locked. Its guard returns a UrlTree for a refused route instead of performing a second navigation as an unrelated side effect. A button changes only the local demonstration flag. No real identity, session or permission service is implemented, and the label makes that limitation clear. A production API must enforce its own authenticated ownership and permission rules.
Run the code
Use a separate empty project folder with a compatible Node version: ^22.22.3, ^24.15.0 or a supported 26+ runtime. The review uses Node 24.22.1, Angular 22.2.4, CLI/build 22.2.5 and TypeScript 6.0.3. Node 22.15.1 is too old for Angular 22. Save every download at its displayed path; browsers may keep only the basename, so recreate the src folder. Run npm install, npm run build and npm start, then open the loopback address printed by the server. Run npm test for the supplied component tests. These are local teaching projects, not Bhylu publishing files.
The small loopback server serves the compiled app and fixed, read-only tool API fixtures. It is not a database, authentication or lending service. Angular examples do not run in Bhylu’s current HTML or TypeScript playground. No Apache, MySQL or PHP configuration change is needed.
Example
import { Component } from '@angular/core';
import { Injectable, inject, signal } from '@angular/core';
import { CanActivateFn, Router, RouterLink, RouterOutlet, Routes } from '@angular/router';
@Injectable({ providedIn: 'root' })
class DemoAccess {
allowed = signal(false);
}
const guard: CanActivateFn = () =>
inject(DemoAccess).allowed() || inject(Router).createUrlTree(['/denied']);
@Component({
selector: 'bh-route-guards-part-0',
standalone: true,
template: '<p data-result>Catalog</p>',
})
class CatalogPage {}
@Component({
selector: 'bh-route-guards-part-1',
standalone: true,
template: '<p data-result>Local access refused</p>',
})
class DeniedPage {}
@Component({
selector: 'bh-route-guards-part-2',
standalone: true,
template: '<p data-result>Demo loans allowed</p>',
})
class LoansPage {}
export const routes: Routes = [
{ path: '', component: CatalogPage },
{ path: 'loans', canActivate: [guard], component: LoansPage },
{ path: 'denied', component: DeniedPage },
];
@Component({
selector: 'bh-route-guards',
standalone: true,
imports: [RouterLink, RouterOutlet],
template: `<button type="button" (click)="access.allowed.set(true)">
Unlock local demo</button
><a routerLink="/loans">Loans</a><router-outlet></router-outlet>`,
})
export class App {
access = inject(DemoAccess);
}
Expected result
Catalog
The review checks the refused route first, enables the demo flag and then checks allowed navigation. This proves both guard branches for the local model. It is not an authentication test or a claim that users cannot bypass a browser-side flag.
Supporting source
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app';
export const appConfig: ApplicationConfig = {
providers: [provideZonelessChangeDetection(), provideRouter(routes)],
};