Skip to content
cccskills
FreePublished on GitHub

angular-routing

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

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md12.1 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Angular Routing

Angular Router is built-in (@angular/router) — no third-party. This skill covers configuration, navigation, guards, lazy loading.

Setup

Standalone (Angular 17+)

// app.routes.ts
import { Routes } from '@angular/router';
import { authGuard } from './core/auth/auth.guard';

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'login', component: LoginComponent },
  {
    path: 'dashboard',
    loadComponent: () => import('./features/dashboard/dashboard.component').then((m) => m.DashboardComponent),
    canActivate: [authGuard],
  },
  {
    path: 'users',
    loadChildren: () => import('./features/users/users.routes').then((m) => m.usersRoutes),
  },
  { path: '**', component: NotFoundComponent },
];
// main.ts (or app.config.ts)
bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes, withComponentInputBinding(), withViewTransitions()),
    provideHttpClient(),
  ],
});

provideRouter features:

  • withComponentInputBinding() — auto-binds route params to component @Input() / input().
  • withViewTransitions() — uses View Transitions API for navigation animations.
  • withInMemoryScrolling({ scrollPositionRestoration: 'enabled' }) — restore scroll on back nav.
  • withDebugTracing() — log all navigation events.

NgModule (legacy)

// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [ /* same shape */ ];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

// AppModule imports AppRoutingModule

<router-outlet> in app.component.html renders the matched component.

Route configuration

{
  path: 'users',
  component: UsersComponent,
  data: { title: 'Users' },              // arbitrary route metadata
  resolve: { users: usersResolver },     // pre-load data
  canActivate: [authGuard],
  canDeactivate: [unsavedChangesGuard],
  children: [
    { path: '', component: UsersListComponent },
    { path: ':id', component: UserDetailComponent, resolve: { user: userResolver } },
    { path: ':id/edit', component: UserEditComponent, canDeactivate: [unsavedChangesGuard] },
  ],
}

Path matching

{ path: '', component: HomeComponent }                     // empty path (root)
{ path: 'users', component: UsersComponent }               // /users
{ path: 'users/:id', component: UserDetailComponent }      // /users/123
{ path: 'users/:id/posts/:postId' }                        // multiple params
{ path: 'docs/**', component: DocsComponent }              // wildcard catch-all
{ path: '**', component: NotFoundComponent }               // global 404 (always last)
{ path: 'old', redirectTo: 'new', pathMatch: 'full' }      // redirect

pathMatch: 'full' for redirects — otherwise path: '' matches every URL.

Lazy loading

Standalone component:

{
  path: 'dashboard',
  loadComponent: () => import('./features/dashboard/dashboard.component').then((m) => m.DashboardComponent),
}

Feature routes file:

// features/users/users.routes.ts
import { Routes } from '@angular/router';
import { UsersListComponent } from './users-list.component';

export const usersRoutes: Routes = [
  { path: '', component: UsersListComponent },
  { path: ':id', loadComponent: () => import('./user-detail.component').then((m) => m.UserDetailComponent) },
];

// app.routes.ts
{
  path: 'users',
  loadChildren: () => import('./features/users/users.routes').then((m) => m.usersRoutes),
}

NgModule (legacy):

{
  path: 'users',
  loadChildren: () => import('./features/users/users.module').then((m) => m.UsersModule),
}

Lazy loading creates a separate JS chunk — first navigation downloads it, subsequent uses cached.

Functional guards (Angular 14.1+, preferred)

// core/auth/auth.guard.ts
import { CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';

export const authGuard: CanActivateFn = (route, state) => {
  const auth = inject(AuthService);
  const router = inject(Router);

  if (auth.isAuthenticated()) {
    return true;
  }

  // Redirect to login with return URL
  router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
  return false;
};

Apply:

{ path: 'dashboard', component: DashboardComponent, canActivate: [authGuard] }

Multiple guards

{ path: 'admin', component: AdminComponent, canActivate: [authGuard, roleGuard('admin')] }

Composed guards run in order; if any returns false / UrlTree, route blocks.

Guard factory pattern

export const roleGuard = (requiredRole: string): CanActivateFn => (route, state) => {
  const auth = inject(AuthService);
  return auth.hasRole(requiredRole);
};

// Usage
{ canActivate: [roleGuard('admin')] }

Other guard types

GuardWhen
canActivateBefore activating route
canActivateChildBefore activating child routes
canDeactivateBefore leaving route (unsaved changes prompt)
canLoadBefore lazy-loading the route module/component (deprecated in favor of canMatch)
canMatchConditionally match the route (Angular 14.2+)
resolvePre-fetch data before route activates

Class-based guards (legacy)

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree {
    // ...
  }
}

Still works but functional API is preferred. Don't migrate existing class guards as part of feature work unless BA spec asks.

Resolvers

Pre-fetch data before route activates. Component receives data via route.data instead of loading inside ngOnInit().

// users.resolver.ts
import { ResolveFn } from '@angular/router';
import { inject } from '@angular/core';
import { UsersService } from './users.service';
import type { User } from './user.model';

export const userResolver: ResolveFn<User> = (route) => {
  const usersService = inject(UsersService);
  const id = route.paramMap.get('id')!;
  return usersService.getUser(id);
};

// In route config
{ path: 'users/:id', component: UserDetailComponent, resolve: { user: userResolver } }

// In component
@Component({...})
export class UserDetailComponent {
  private route = inject(ActivatedRoute);
  user = this.route.snapshot.data['user'] as User;       // sync access
  // OR reactive
  user$ = this.route.data.pipe(map((data) => data['user'] as User));
}

Resolvers return Observable, Promise, or value. Navigation pauses until resolution completes.

Programmatic navigation

import { Router } from '@angular/router';
import { inject } from '@angular/core';

@Component({...})
export class MyComponent {
  private router = inject(Router);

  goToProfile(id: string) {
    this.router.navigate(['/users', id]);
  }

  goRelative() {
    this.router.navigate(['..'], { relativeTo: this.route });
  }

  navigateWithQuery() {
    this.router.navigate(['/search'], { queryParams: { q: 'hello', page: 1 } });
  }

  replaceUrl() {
    this.router.navigate(['/login'], { replaceUrl: true });
  }
}

router.navigate(commands, extras?) — commands array; first element is path, rest are segments.

router.navigateByUrl('/users/123?tab=overview') — string URL alternative.

Reading params and queries

Snapshot (sync, only initial)

private route = inject(ActivatedRoute);

ngOnInit() {
  const id = this.route.snapshot.paramMap.get('id');
  const tab = this.route.snapshot.queryParamMap.get('tab') ?? 'overview';
}

Snapshot reflects route at component creation. Won't update if user navigates within same component.

Observables (reactive)

this.route.paramMap
  .pipe(takeUntilDestroyed())
  .subscribe((params) => {
    this.id = params.get('id')!;
  });

this.route.queryParamMap
  .pipe(takeUntilDestroyed())
  .subscribe((qp) => {
    this.tab = qp.get('tab') ?? 'overview';
  });

Updates on every route change within same component instance.

Signals (Angular 16+)

import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs';

id = toSignal(this.route.paramMap.pipe(map((p) => p.get('id') ?? '')), { initialValue: '' });
tab = toSignal(this.route.queryParamMap.pipe(map((q) => q.get('tab') ?? 'overview')), { initialValue: 'overview' });

Use signals when consuming in computed / template:

title = computed(() => `User ${this.id()}`);

withComponentInputBinding() (Angular 16+)

provideRouter(routes, withComponentInputBinding()),

Route params auto-bind to component inputs:

@Component({ ... })
export class UserDetailComponent {
  @Input() id!: string;                  // bound from route param :id
  @Input() tab = 'overview';             // bound from query ?tab=
}

In Angular 17.1+ with signal inputs:

id = input.required<string>();           // signal version
tab = input<string>('overview');

Cleanest pattern. No need for ActivatedRoute injection in many cases.

RouterLink

<a routerLink="/users">Users</a>
<a [routerLink]="['/users', user.id]">{{ user.name }}</a>
<a [routerLink]="['/search']" [queryParams]="{ q: 'hello' }">Search</a>
<a routerLink="/dashboard" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">Dashboard</a>

For external URLs, use plain <a href="..."> — routerLink is for internal nav only.

Route metadata via data

{ path: 'admin', component: AdminComponent, data: { title: 'Admin', requiresAuth: true } }

Read in component:

this.route.data.subscribe((data) => {
  this.title = data['title'];
});

For app-wide title management:

// In root component
this.router.events
  .pipe(
    filter((e) => e instanceof NavigationEnd),
    takeUntilDestroyed()
  )
  .subscribe(() => {
    let route = this.route;
    while (route.firstChild) route = route.firstChild;
    const title = route.snapshot.data['title'] ?? 'My App';
    this.titleService.setTitle(title);
  });

Or use provideRouter(routes, withRouterConfig({ paramsInheritanceStrategy: 'always' })) + Title service.

Or Angular 14.1+: title route property, set automatically:

{ path: 'admin', component: AdminComponent, title: 'Admin | My App' }
{ path: 'users/:id', component: UserDetailComponent, title: userTitleResolver }

Anti-patterns

  • ❌ Forgetting <router-outlet> in layout — routes don't render.
  • ❌ <a href="/internal"> for internal nav — full reload, defeats SPA.
  • ❌ Hardcoded route strings throughout app — define route constants in app.routes.ts or shared file.
  • ❌ Subscribing to route.params without takeUntilDestroyed() — memory leak.
  • ❌ Heavy logic in resolvers — slows navigation; keep resolvers thin.
  • ❌ Returning Observable<never> from a guard without UrlTree — guard hangs.
  • ❌ pathMatch: 'full' missing on redirect — redirect catches every URL.
  • ❌ Class-based guards in new Angular 17+ projects — functional API is cleaner.
  • ❌ Multiple <router-outlet> without named outlets — only the first activates.
  • ❌ Lazy-loading single-component routes when not needed — premature optimization for small components.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer. Use this skill to: - Detect project style (standalone vs NgModule) and apply matching patterns. - Pick correct decorators and DI approach. - Use modern control flow (@if/@for/@switch) in Angular 17+ projects. - Apply `inject()` function over constructor injection where appropriate. - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy). Do NOT use this skill for: - State management (see angular-state-and-rx). - Routing (see angular-routing). - Forms (see angular-forms). - Testing (see angular-testing).

The English summary is being prepared. Showing the original description.

AratKruglik/claude-sdlc43Updated Oct 17, 2026

Angular forms: Reactive Forms (preferred — typed FormGroup/FormControl since Angular 14, FormBuilder, custom + async validators, FormArray, multi-step) and Template-driven (`[(ngModel)]` + FormsModule). Validation strategies, server error mapping, accessibility. Use this skill to: - Build Reactive Forms with typed FormGroup/FormControl. - Use FormBuilder для concise syntax. - Implement custom synchronous and async validators. - Wire FormArray for dynamic field lists. - Map server errors back to form fields. - Pick Reactive vs Template-driven (prefer Reactive). Do NOT use this skill for: - General conventions (see angular-conventions). - State management beyond forms (see angular-state-and-rx). - Routing (see angular-routing). - Testing forms (see angular-testing).

The English summary is being prepared. Showing the original description.

AratKruglik/claude-sdlc43Updated Oct 17, 2026

State management for Angular 18-21: signals (signal/computed/effect), services-as-state, NgRx Store + Effects + Selectors, NgRx Component Store, NgRx Signals (newer signal-based store). RxJS essentials — operators, async pipe, takeUntilDestroyed, signal/observable interop. Use this skill to: - Pick the right state tool (signals / services / NgRx variant / vue-query equivalent). - Use signals correctly (signal/computed/effect — when each). - Build a Pinia-style service-as-state singleton. - Set up NgRx Store + Effects + Selectors. - Use RxJS without leaking subscriptions (async pipe, takeUntilDestroyed, Subject patterns). - Bridge signals ↔ observables via toSignal / toObservable. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing state (see angular-routing). - Form state (see angular-forms). - Testing state (see angular-testing).

The English summary is being prepared. Showing the original description.

AratKruglik/claude-sdlc43Updated Oct 17, 2026

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Test NgRx Effects with provideMockActions. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing patterns broadly (see angular-routing — covers testing routes briefly). - Form patterns broadly (see angular-forms).

The English summary is being prepared. Showing the original description.

AratKruglik/claude-sdlc43Updated Oct 17, 2026

Shared conventions for every SDLC development-phase architect agent: hard rules, code quality bar, workflow steps (superpowers invocation, spec reading, codebase exploration, verification), and the report/compact-summary contract. Architects load this skill first, then apply their stack-specific instructions on top.

The English summary is being prepared. Showing the original description.

AratKruglik/claude-sdlc43Updated Oct 17, 2026

ASP.NET Core web framework conventions: Minimal API vs MVC controllers, Program.cs composition, DI lifetimes (Scoped/Singleton/Transient), Options pattern with IOptions<T>, middleware ordering (HTTPS redirect → routing → authentication → authorization), model binding and validation (FluentValidation / DataAnnotations), ProblemDetails error handling, structured logging with ILogger<T>, configuration layering (appsettings.json + environment variables + User Secrets), and health checks. Works alongside csharp-foundation:csharp-conventions and aspnet-core-plugin:efcore-patterns. Use this skill to: - Compose Program.cs correctly — register services, configure middleware in the right order, map endpoints. - Apply the Options pattern to avoid passing raw IConfiguration into services. - Write Minimal API endpoint groups with typed results and authorization. - Handle cross-cutting errors uniformly with ProblemDetails. - Configure structured logging and health checks for production readiness. Do NOT use this skill for: - EF Core entity configuration and migrations — see aspnet-core-plugin:efcore-patterns. - C# language idioms — see csharp-foundation:csharp-conventions. - Testing — see csharp-foundation:dotnet-testing.

The English summary is being prepared. Showing the original description.

AratKruglik/claude-sdlc43Updated Oct 17, 2026

See all skills by AratKruglik

Report a problem with this skill