home / skills / pluginagentmarketplace / custom-plugin-angular / routing

routing skill

/skills/routing

This skill helps optimize Angular routing performance by enabling lazy loading, guards, preloading, change detection tuning, and bundle analysis.

npx playbooks add skill pluginagentmarketplace/custom-plugin-angular --skill routing

Review the files below or copy the command above to add this skill to your agents.

Files (10)
SKILL.md
9.9 KB
---
name: routing-performance-implementation
description: Configure routing with lazy loading, implement route guards, set up preloading strategies, optimize change detection, analyze bundles, and implement performance optimizations.
sasmp_version: "1.3.0"
bonded_agent: 05-routing-performance
bond_type: PRIMARY_BOND
---

# Routing & Performance Implementation Skill

## Quick Start

### Basic Routing
```typescript
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent, AboutComponent, NotFoundComponent } from './components';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: '**', component: NotFoundComponent }
];

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

### Navigation
```typescript
import { Component } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  template: `
    <button (click)="goHome()">Home</button>
    <a routerLink="/about">About</a>
    <a routerLink="/users" [queryParams]="{ tab: 'active' }">Users</a>
  `
})
export class NavComponent {
  constructor(private router: Router) {}

  goHome() {
    this.router.navigate(['/']);
  }
}
```

### Route Parameters
```typescript
const routes: Routes = [
  { path: 'users/:id', component: UserDetailComponent },
  { path: 'users/:id/posts/:postId', component: PostDetailComponent }
];

// Component
@Component({...})
export class UserDetailComponent {
  userId!: string;

  constructor(private route: ActivatedRoute) {
    this.route.params.subscribe(params => {
      this.userId = params['id'];
    });
  }
}

// Or with snapshot
ngOnInit() {
  const id = this.route.snapshot.params['id'];
}
```

## Lazy Loading

### Feature Modules with Lazy Loading
```typescript
// app-routing.module.ts
const routes: Routes = [
  { path: '', component: HomeComponent },
  {
    path: 'users',
    loadChildren: () => import('./users/users.module').then(m => m.UsersModule)
  },
  {
    path: 'products',
    loadChildren: () => import('./products/products.module').then(m => m.ProductsModule)
  }
];

// users/users-routing.module.ts
const routes: Routes = [
  { path: '', component: UserListComponent },
  { path: ':id', component: UserDetailComponent }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class UsersRoutingModule { }
```

### Lazy Loading with Standalone Components
```typescript
const routes: Routes = [
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES)
  }
];

// admin/admin.routes.ts
export const ADMIN_ROUTES: Routes = [
  { path: '', component: AdminDashboardComponent },
  { path: 'users', component: AdminUsersComponent }
];
```

## Route Guards

### CanActivate Guard
```typescript
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';
import { map } from 'rxjs/operators';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private authService: AuthService,
    private router: Router
  ) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> {
    return this.authService.isAuthenticated$.pipe(
      map(isAuth => {
        if (isAuth) return true;
        this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
        return false;
      })
    );
  }
}

// Usage
const routes: Routes = [
  { path: 'admin', component: AdminComponent, canActivate: [AuthGuard] }
];
```

### CanDeactivate Guard
```typescript
export interface CanComponentDeactivate {
  canDeactivate: () => Observable<boolean> | boolean;
}

@Injectable()
export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
  canDeactivate(component: CanComponentDeactivate): Observable<boolean> | boolean {
    return component.canDeactivate();
  }
}

// Component
@Component({...})
export class FormComponent implements CanComponentDeactivate {
  form!: FormGroup;

  canDeactivate(): Observable<boolean> | boolean {
    return !this.form.dirty || confirm('Discard changes?');
  }
}

// Usage
{ path: 'form', component: FormComponent, canDeactivate: [CanDeactivateGuard] }
```

### Resolve Guard
```typescript
@Injectable()
export class UserResolver implements Resolve<User> {
  constructor(private userService: UserService) {}

  resolve(route: ActivatedRouteSnapshot): Observable<User> {
    return this.userService.getUser(route.params['id']);
  }
}

// Usage
{
  path: 'users/:id',
  component: UserDetailComponent,
  resolve: { user: UserResolver }
}

// Component receives data
@Component({...})
export class UserDetailComponent {
  user!: User;

  constructor(private route: ActivatedRoute) {
    this.route.data.subscribe(data => {
      this.user = data['user'];
    });
  }
}
```

## Query Parameters

```typescript
// Navigation
this.router.navigate(['/users'], {
  queryParams: {
    page: 1,
    sort: 'name',
    filter: 'active'
  }
});

// Reading
this.route.queryParams.subscribe(params => {
  const page = params['page'];
  const sort = params['sort'];
});

// Template
<a [routerLink]="['/users']" [queryParams]="{ page: 2, sort: 'name' }">
  Next Page
</a>
```

## Fragment (Hash)

```typescript
// Navigation
this.router.navigate(['/docs'], { fragment: 'section1' });

// Reading
this.route.fragment.subscribe(fragment => {
  console.log('Fragment:', fragment);
});

// Template
<a routerLink="/docs" fragment="section1">Section 1</a>
```

## Preloading Strategies

```typescript
// Default: no preloading
RouterModule.forRoot(routes);

// Preload all lazy modules
RouterModule.forRoot(routes, {
  preloadingStrategy: PreloadAllModules
});

// Custom preloading strategy
@Injectable()
export class SelectivePreloadingStrategy implements PreloadingStrategy {
  preload(route: Route, load: () => Observable<any>): Observable<any> {
    if (route.data && route.data['preload']) {
      return load();
    }
    return of(null);
  }
}

// Usage
const routes: Routes = [
  { path: 'users', loadChildren: '...', data: { preload: true } }
];

RouterModule.forRoot(routes, {
  preloadingStrategy: SelectivePreloadingStrategy
})
```

## Route Reuse Strategy

```typescript
@Injectable()
export class CustomRouteReuseStrategy implements RouteReuseStrategy {
  storedRoutes: { [key: string]: RouteData } = {};

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return route.data['cache'] === true;
  }

  store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {
    this.storedRoutes[route.url.join('/')] = { route, handle: detachedTree };
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return !!this.storedRoutes[route.url.join('/')];
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
    return this.storedRoutes[route.url.join('/')]?.handle || null;
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, current: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === current.routeConfig;
  }
}
```

## Performance Optimization

### Code Splitting
```typescript
// Only load admin module when needed
{
  path: 'admin',
  loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}
```

### Change Detection with Routes
```typescript
@Component({
  selector: 'app-root',
  template: `<router-outlet></router-outlet>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent { }
```

### Scroll Position

```typescript
// Scroll to top on route change
RouterModule.forRoot(routes, {
  scrollPositionRestoration: 'top'
})

// Or custom scroll
export class ScrollToTopComponent implements OnInit {
  constructor(private router: Router) {}

  ngOnInit() {
    this.router.events.pipe(
      filter(event => event instanceof NavigationEnd)
    ).subscribe(() => {
      window.scrollTo(0, 0);
    });
  }
}
```

## Advanced Patterns

### Auxiliary Routes
```typescript
// URL: /users/1(admin:admin-panel)
<router-outlet></router-outlet>
<router-outlet name="admin"></router-outlet>

// Navigation
this.router.navigate([
  { outlets: {
    primary: ['users', userId],
    admin: ['admin-panel']
  }}
]);
```

### Child Routes with Components
```typescript
const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    children: [
      { path: 'stats', component: StatsComponent },
      { path: 'reports', component: ReportsComponent }
    ]
  }
];

// DashboardComponent template
<nav>
  <a routerLink="stats" routerLinkActive="active">Stats</a>
  <a routerLink="reports" routerLinkActive="active">Reports</a>
</nav>
<router-outlet></router-outlet>
```

## Testing Routes

```typescript
describe('Routing', () => {
  let router: Router;
  let location: Location;
  let fixture: ComponentFixture<AppComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [AppRoutingModule, AppComponent]
    }).compileComponents();

    router = TestBed.inject(Router);
    location = TestBed.inject(Location);
    fixture = TestBed.createComponent(AppComponent);
  });

  it('should navigate to home', fakeAsync(() => {
    router.navigate(['']);
    tick();
    expect(location.path()).toBe('/');
  }));
});
```

## Best Practices

1. **Lazy load features**: Reduce initial bundle size
2. **Use route guards**: Control access and preload data
3. **Implement RouteReuseStrategy**: Cache components when needed
4. **Handle 404s**: Provide meaningful error pages
5. **Query params for filters**: Keep state in URL
6. **Preload strategically**: Balance performance vs initial load
7. **Use fragments for anchors**: Scroll to page sections

## Resources

- [Angular Routing Guide](https://angular.io/guide/router)
- [Route Guards](https://angular.io/guide/router-tutorial-toh)
- [Lazy Loading](https://angular.io/guide/lazy-loading-ngmodules)

Overview

This skill packages practical guidance and ready-to-use patterns for Angular routing and performance. It guides configuration of routes, lazy loading, route guards, preloading strategies, change detection optimizations, bundle analysis, and common performance fixes. The content targets implementers who want scalable navigation and faster load times.

How this skill works

The skill inspects routing needs and provides code patterns to register routes, lazy-load feature modules or standalone components, and attach route guards (CanActivate, CanDeactivate, Resolve). It shows how to configure preloading strategies, custom route reuse, scroll restoration, and OnPush change detection to reduce work during navigation. It also documents testing approaches and advanced patterns like auxiliary and child routes to organize complex UIs.

When to use it

  • When initial bundle size is large and features should load on demand
  • When routes require access control, confirmation on exit, or prefetched data
  • When navigation performance or scroll restoration needs consistent behavior
  • When certain route components should be cached or reused to avoid expensive re-initialization
  • When balancing startup time vs. perceived responsiveness with preloading

Best practices

  • Lazy-load feature modules and standalone routes to reduce initial payload
  • Use CanActivate/Resolve to protect routes and preload essential data before activation
  • Adopt a custom RouteReuseStrategy for expensive component trees you want to cache
  • Prefer ChangeDetectionStrategy.OnPush at top-level routed components to limit checks
  • Apply selective preloading (custom strategy) to load high-value modules in background
  • Handle 404 routes and keep filter/sort state in query params for shareable URLs

Example use cases

  • Protect an admin area with an AuthGuard and redirect unauthenticated users to login with returnUrl
  • Lazy-load an e-commerce product module and selectively preload cart or account modules
  • Implement CanDeactivate on complex forms to confirm navigation when unsaved changes exist
  • Use RouteReuseStrategy to cache a heavy list view so navigating back is instant
  • Configure scrollPositionRestoration or custom scroll handling for consistent UX after navigation

FAQ

How do I decide which modules to lazy load vs preload?

Lazy load feature modules that are not needed at startup (admin, reports). Use selective preloading for modules users often access after first load to improve perceived performance.

When should I implement a RouteReuseStrategy?

Implement reuse when routed components are expensive to recreate (complex lists, heavy DOM) and you want instant back/forward navigation. Keep logic simple and keyed by route URL or routeConfig to avoid stale state.