AgentSkillsCN

angular-best-practices

Angular 性能优化指南与最佳实践。适用于编写、审查或重构 Angular 代码时,以实现最优性能、最小化包体积并提升渲染效率。

SKILL.md
--- frontmatter
name: angular-best-practices
description: Angular performance optimization and best practices guide. Use when writing, reviewing, or refactoring Angular code for optimal performance, bundle size, and rendering efficiency. 
category: AI & Agents
source: antigravity
tags: [typescript, api, ai, template, cro]
url: https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/angular-best-practices

Angular Best Practices

Comprehensive performance optimization guide for Angular applications. Contains prioritized rules for eliminating performance bottlenecks, optimizing bundles, and improving rendering.

When to Apply

Reference these guidelines when:

  • Writing new Angular components or pages
  • Implementing data fetching patterns
  • Reviewing code for performance issues
  • Refactoring existing Angular code
  • Optimizing bundle size or load times
  • Configuring SSR/hydration

Rule Categories by Priority

PriorityCategoryImpactFocus
1Change DetectionCRITICALSignals, OnPush, Zoneless
2Async WaterfallsCRITICALRxJS patterns, SSR preloading
3Bundle OptimizationCRITICALLazy loading, tree shaking
4Rendering PerformanceHIGH@defer, trackBy, virtualization
5Server-Side RenderingHIGHHydration, prerendering
6Template OptimizationMEDIUMControl flow, pipes
7State ManagementMEDIUMSignal patterns, selectors
8Memory ManagementLOW-MEDIUMCleanup, subscriptions

1. Change Detection (CRITICAL)

Use OnPush Change Detection

typescript
// CORRECT - OnPush with Signals
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<div>{{ count() }}</div>`,
})
export class CounterComponent {
  count = signal(0);
}

// WRONG - Default change detection
@Component({
  template: `<div>{{ count }}</div>`, // Checked every cycle
})
export class CounterComponent {
  count = 0;
}

Prefer Signals Over Mutable Properties

typescript
// CORRECT - Signals trigger precise updates
@Component({
  template: `
    <h1>{{ title() }}</h1>
    <p>Count: {{ count() }}</p>
  `,
})
export class DashboardComponent {
  title = signal("Dashboard");
  count = signal(0);
}

// WRONG - Mutable properties require zone.js checks
@Component({
  template: `
    <h1>{{ title }}</h1>
    <p>Count: {{ count }}</p>
  `,
})
export class DashboardComponent {
  title = "Dashboard";
  count = 0;
}

Enable Zoneless for New Projects

typescript
// main.ts - Zoneless Angular (v20+)
bootstrapApplication(AppComponent, {
  providers: [provideZonelessChangeDetection()],
});

Benefits:

  • No zone.js patches on async APIs
  • Smaller bundle (~15KB savings)
  • Clean stack traces for debugging
  • Better micro-frontend compatibility

2. Async Operations & Waterfalls (CRITICAL)

Eliminate Sequential Data Fetching

typescript
// WRONG - Nested subscriptions create waterfalls
this.route.params.subscribe((params) => {
  // 1. Wait for params
  this.userService.getUser(params.id).subscribe((user) => {
    // 2. Wait for user
    this.postsService.getPosts(user.id).subscribe((posts) => {
      // 3. Wait for posts
    });
  });
});

// CORRECT - Parallel execution with forkJoin
forkJoin({
  user: this.userService.getUser(id),
  posts: this.postsService.getPosts(id),
}).subscribe((data) => {
  // Fetched in parallel
});

// CORRECT - Flatten dependent calls with switchMap
this.route.params
  .pipe(
    map((p) => p.id),
    switchMap((id) => this.userService.getUser(id)),
  )
  .subscribe();

Avoid Client-Side Waterfalls in SSR

typescript
// CORRECT - Use resolvers or blocking hydration for critical data
export const route: Route = {
  path: "profile/:id",
  resolve: { data: profileResolver }, // Fetched on server before navigation
  component: ProfileComponent,
};

// WRONG - Component fetches data on init
class ProfileComponent implements OnInit {
  ngOnInit() {
    // Starts ONLY after JS loads and component renders
    this.http.get("/api/profile").subscribe();
  }
}

3. Bundle Optimization (CRITICAL)

Lazy Load Routes

typescript
// CORRECT - Lazy load feature routes
export const routes: Routes = [
  {
    path: "admin",
    loadChildren: () =>
      import("./admin/admin.routes").then((m) => m.ADMIN_ROUTES),
  },
  {
    path: "dashboard",
    loadComponent: () =>
      import("./dashboard/dashboard.component").then(
        (m) => m.DashboardComponent,
      ),
  },
];

// WRONG - Eager loading everything
import { AdminModule } from "./admin/admin.module";
export const routes: Routes = [
  { path: "admin", component: AdminComponent }, // In main bundle
];

Use @defer for Heavy Components

html
<!-- CORRECT - Heavy component loads on demand -->
@defer (on viewport) {
<app-analytics-chart [data]="data()" />
} @placeholder {
<div class="chart-skeleton"></div>
}

<!-- WRONG - Heavy component in initial bundle -->
<app-analytics-chart [data]="data()" />

Avoid Barrel File Re-exports

typescript
// WRONG - Imports entire barrel, breaks tree-shaking
import { Button, M