Agent skill
routing-performance-implementation
Configure routing with lazy loading, implement route guards, set up preloading strategies, optimize change detection, analyze bundles, and implement performance optimizations.
Install this agent skill to your Project
npx add-skill https://github.com/pluginagentmarketplace/custom-plugin-angular/tree/main/skills/routing
SKILL.md
Routing & Performance Implementation Skill
Quick Start
Basic Routing
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
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
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
// 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
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
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
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
@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
// 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)
// 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
// 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
@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
// Only load admin module when needed
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}
Change Detection with Routes
@Component({
selector: 'app-root',
template: `<router-outlet></router-outlet>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent { }
Scroll Position
// 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
// 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
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
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
- Lazy load features: Reduce initial bundle size
- Use route guards: Control access and preload data
- Implement RouteReuseStrategy: Cache components when needed
- Handle 404s: Provide meaningful error pages
- Query params for filters: Keep state in URL
- Preload strategically: Balance performance vs initial load
- Use fragments for anchors: Scroll to page sections
Resources
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
angular-core-implementation
Generate Angular components, services, modules, and directives. Implement dependency injection, lifecycle hooks, data binding, and build production-ready Angular architectures.
testing-deployment-implementation
Write unit tests for components and services, implement E2E tests with Cypress, set up test mocks, optimize production builds, configure CI/CD pipelines, and deploy to production platforms.
state-implementation
Implement NgRx store with actions and reducers, build selectors, create effects for async operations, configure entity adapters, and integrate HTTP APIs with state management.
modern-angular
http-client
Angular HttpClient for API communication, interceptors, and error handling
deployment
Angular application deployment, CI/CD pipelines, and hosting strategies
Didn't find tool you were looking for?