[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6EX8RTFAJVKDWZCZR0M2N43":3,"skill-same:01M6EX8RTFAJVKDWZCZR0M2N43":79},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":37,"linkOnly":38,"markdownContent":39,"markdownBody":40,"frontMatter":41,"refName":42,"commitSha":43,"skillDirectory":44,"forksCount":45,"repositoryHtmlUrl":46,"repositoryHomepageUrl":47,"ownerHtmlUrl":48,"ownerBlogUrl":6,"ownerDescription":49,"updatedAt":50,"install":51,"resources":64},"01M6EX8RTFAJVKDWZCZR0M2N43","angular-best-practices",null,"Angular performance optimization and best practices guide. Use when writing, reviewing, or refactoring Angular code for optimal performance, bundle size, and rendering efficiency.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fsickn33\u002Fangular-best-practices\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],"best practices","angular","angular best practices","bundle size","angular performance","rendering","bundle","efficiency","self","use","guide","writing","performance optimization","refactoring","Optimization","code","performance","safe","2026-11-10 10:12:30",false,"---\nname: angular-best-practices\ndescription: Angular performance optimization and best practices guide. Use when writing, reviewing, or refactoring Angular code for optimal performance, bundle size, and rendering efficiency.\nrisk: safe\nsource: self\n---\n\n# Angular Best Practices\n\nComprehensive performance optimization guide for Angular applications. Contains prioritized rules for eliminating performance bottlenecks, optimizing bundles, and improving rendering.\n\n## When to Apply\n\nReference these guidelines when:\n\n- Writing new Angular components or pages\n- Implementing data fetching patterns\n- Reviewing code for performance issues\n- Refactoring existing Angular code\n- Optimizing bundle size or load times\n- Configuring SSR\u002Fhydration\n\n---\n\n## Rule Categories by Priority\n\n| Priority | Category              | Impact     | Focus                           |\n| -------- | --------------------- | ---------- | ------------------------------- |\n| 1        | Change Detection      | CRITICAL   | Signals, OnPush, Zoneless       |\n| 2        | Async Waterfalls      | CRITICAL   | RxJS patterns, SSR preloading   |\n| 3        | Bundle Optimization   | CRITICAL   | Lazy loading, tree shaking      |\n| 4        | Rendering Performance | HIGH       | @defer, trackBy, virtualization |\n| 5        | Server-Side Rendering | HIGH       | Hydration, prerendering         |\n| 6        | Template Optimization | MEDIUM     | Control flow, pipes             |\n| 7        | State Management      | MEDIUM     | Signal patterns, selectors      |\n| 8        | Memory Management     | LOW-MEDIUM | Cleanup, subscriptions          |\n\n---\n\n## 1. Change Detection (CRITICAL)\n\n### Use OnPush Change Detection\n\n```typescript\n\u002F\u002F CORRECT - OnPush with Signals\n@Component({\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  template: `\u003Cdiv>{{ count() }}\u003C\u002Fdiv>`,\n})\nexport class CounterComponent {\n  count = signal(0);\n}\n\n\u002F\u002F WRONG - Default change detection\n@Component({\n  template: `\u003Cdiv>{{ count }}\u003C\u002Fdiv>`, \u002F\u002F Checked every cycle\n})\nexport class CounterComponent {\n  count = 0;\n}\n```\n\n### Prefer Signals Over Mutable Properties\n\n```typescript\n\u002F\u002F CORRECT - Signals trigger precise updates\n@Component({\n  template: `\n    \u003Ch1>{{ title() }}\u003C\u002Fh1>\n    \u003Cp>Count: {{ count() }}\u003C\u002Fp>\n  `,\n})\nexport class DashboardComponent {\n  title = signal(\"Dashboard\");\n  count = signal(0);\n}\n\n\u002F\u002F WRONG - Mutable properties require zone.js checks\n@Component({\n  template: `\n    \u003Ch1>{{ title }}\u003C\u002Fh1>\n    \u003Cp>Count: {{ count }}\u003C\u002Fp>\n  `,\n})\nexport class DashboardComponent {\n  title = \"Dashboard\";\n  count = 0;\n}\n```\n\n### Enable Zoneless for New Projects\n\n```typescript\n\u002F\u002F main.ts - Zoneless Angular (v20+)\nbootstrapApplication(AppComponent, {\n  providers: [provideZonelessChangeDetection()],\n});\n```\n\n**Benefits:**\n\n- No zone.js patches on async APIs\n- Smaller bundle (~15KB savings)\n- Clean stack traces for debugging\n- Better micro-frontend compatibility\n\n---\n\n## 2. Async Operations & Waterfalls (CRITICAL)\n\n### Eliminate Sequential Data Fetching\n\n```typescript\n\u002F\u002F WRONG - Nested subscriptions create waterfalls\nthis.route.params.subscribe((params) => {\n  \u002F\u002F 1. Wait for params\n  this.userService.getUser(params.id).subscribe((user) => {\n    \u002F\u002F 2. Wait for user\n    this.postsService.getPosts(user.id).subscribe((posts) => {\n      \u002F\u002F 3. Wait for posts\n    });\n  });\n});\n\n\u002F\u002F CORRECT - Parallel execution with forkJoin\nforkJoin({\n  user: this.userService.getUser(id),\n  posts: this.postsService.getPosts(id),\n}).subscribe((data) => {\n  \u002F\u002F Fetched in parallel\n});\n\n\u002F\u002F CORRECT - Flatten dependent calls with switchMap\nthis.route.params\n  .pipe(\n    map((p) => p.id),\n    switchMap((id) => this.userService.getUser(id)),\n  )\n  .subscribe();\n```\n\n### Avoid Client-Side Waterfalls in SSR\n\n```typescript\n\u002F\u002F CORRECT - Use resolvers or blocking hydration for critical data\nexport const route: Route = {\n  path: \"profile\u002F:id\",\n  resolve: { data: profileResolver }, \u002F\u002F Fetched on server before navigation\n  component: ProfileComponent,\n};\n\n\u002F\u002F WRONG - Component fetches data on init\nclass ProfileComponent implements OnInit {\n  ngOnInit() {\n    \u002F\u002F Starts ONLY after JS loads and component renders\n    this.http.get(\"\u002Fapi\u002Fprofile\").subscribe();\n  }\n}\n```\n\n---\n\n## 3. Bundle Optimization (CRITICAL)\n\n### Lazy Load Routes\n\n```typescript\n\u002F\u002F CORRECT - Lazy load feature routes\nexport const routes: Routes = [\n  {\n    path: \"admin\",\n    loadChildren: () =>\n      import(\".\u002Fadmin\u002Fadmin.routes\").then((m) => m.ADMIN_ROUTES),\n  },\n  {\n    path: \"dashboard\",\n    loadComponent: () =>\n      import(\".\u002Fdashboard\u002Fdashboard.component\").then(\n        (m) => m.DashboardComponent,\n      ),\n  },\n];\n\n\u002F\u002F WRONG - Eager loading everything\nimport { AdminModule } from \".\u002Fadmin\u002Fadmin.module\";\nexport const routes: Routes = [\n  { path: \"admin\", component: AdminComponent }, \u002F\u002F In main bundle\n];\n```\n\n### Use @defer for Heavy Components\n\n```html\n\u003C!-- CORRECT - Heavy component loads on demand -->\n@defer (on viewport) {\n\u003Capp-analytics-chart [data]=\"data()\" \u002F>\n} @placeholder {\n\u003Cdiv class=\"chart-skeleton\">\u003C\u002Fdiv>\n}\n\n\u003C!-- WRONG - Heavy component in initial bundle -->\n\u003Capp-analytics-chart [data]=\"data()\" \u002F>\n```\n\n### Avoid Barrel File Re-exports\n\n```typescript\n\u002F\u002F WRONG - Imports entire barrel, breaks tree-shaking\nimport { Button, Modal, Table } from \"@shared\u002Fcomponents\";\n\n\u002F\u002F CORRECT - Direct imports\nimport { Button } from \"@shared\u002Fcomponents\u002Fbutton\u002Fbutton.component\";\nimport { Modal } from \"@shared\u002Fcomponents\u002Fmodal\u002Fmodal.component\";\n```\n\n### Dynamic Import Third-Party Libraries\n\n```typescript\n\u002F\u002F CORRECT - Load heavy library on demand\nasync loadChart() {\n  const { Chart } = await import('chart.js');\n  this.chart = new Chart(this.canvas, config);\n}\n\n\u002F\u002F WRONG - Bundle Chart.js in main chunk\nimport { Chart } from 'chart.js';\n```\n\n---\n\n## 4. Rendering Performance (HIGH)\n\n### Always Use trackBy with @for\n\n```html\n\u003C!-- CORRECT - Efficient DOM updates -->\n@for (item of items(); track item.id) {\n\u003Capp-item-card [item]=\"item\" \u002F>\n}\n\n\u003C!-- WRONG - Entire list re-renders on any change -->\n@for (item of items(); track $index) {\n\u003Capp-item-card [item]=\"item\" \u002F>\n}\n```\n\n### Use Virtual Scrolling for Large Lists\n\n```typescript\nimport { CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll } from '@angular\u002Fcdk\u002Fscrolling';\n\n@Component({\n  imports: [CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll],\n  template: `\n    \u003Ccdk-virtual-scroll-viewport itemSize=\"50\" class=\"viewport\">\n      \u003Cdiv *cdkVirtualFor=\"let item of items\" class=\"item\">\n        {{ item.name }}\n      \u003C\u002Fdiv>\n    \u003C\u002Fcdk-virtual-scroll-viewport>\n  `\n})\n```\n\n### Prefer Pure Pipes Over Methods\n\n```typescript\n\u002F\u002F CORRECT - Pure pipe, memoized\n@Pipe({ name: 'filterActive', standalone: true, pure: true })\nexport class FilterActivePipe implements PipeTransform {\n  transform(items: Item[]): Item[] {\n    return items.filter(i => i.active);\n  }\n}\n\n\u002F\u002F Template\n@for (item of items() | filterActive; track item.id) { ... }\n\n\u002F\u002F WRONG - Method called every change detection\n@for (item of getActiveItems(); track item.id) { ... }\n```\n\n### Use computed() for Derived Data\n\n```typescript\n\u002F\u002F CORRECT - Computed, cached until dependencies change\nexport class ProductStore {\n  products = signal\u003CProduct[]>([]);\n  filter = signal('');\n\n  filteredProducts = computed(() => {\n    const f = this.filter().toLowerCase();\n    return this.products().filter(p =>\n      p.name.toLowerCase().includes(f)\n    );\n  });\n}\n\n\u002F\u002F WRONG - Recalculates every access\nget filteredProducts() {\n  return this.products.filter(p =>\n    p.name.toLowerCase().includes(this.filter)\n  );\n}\n```\n\n---\n\n## 5. Server-Side Rendering (HIGH)\n\n### Configure Incremental Hydration\n\n```typescript\n\u002F\u002F app.config.ts\nimport {\n  provideClientHydration,\n  withIncrementalHydration,\n} from \"@angular\u002Fplatform-browser\";\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideClientHydration(withIncrementalHydration(), withEventReplay()),\n  ],\n};\n```\n\n### Defer Non-Critical Content\n\n```html\n\u003C!-- Critical above-the-fold content -->\n\u003Capp-header \u002F>\n\u003Capp-hero \u002F>\n\n\u003C!-- Below-fold deferred with hydration triggers -->\n@defer (hydrate on viewport) {\n\u003Capp-product-grid \u002F>\n} @defer (hydrate on interaction) {\n\u003Capp-chat-widget \u002F>\n}\n```\n\n### Use TransferState for SSR Data\n\n```typescript\n@Injectable({ providedIn: \"root\" })\nexport class DataService {\n  private http = inject(HttpClient);\n  private transferState = inject(TransferState);\n  private platformId = inject(PLATFORM_ID);\n\n  getData(key: string): Observable\u003CData> {\n    const stateKey = makeStateKey\u003CData>(key);\n\n    if (isPlatformBrowser(this.platformId)) {\n      const cached = this.transferState.get(stateKey, null);\n      if (cached) {\n        this.transferState.remove(stateKey);\n        return of(cached);\n      }\n    }\n\n    return this.http.get\u003CData>(`\u002Fapi\u002F${key}`).pipe(\n      tap((data) => {\n        if (isPlatformServer(this.platformId)) {\n          this.transferState.set(stateKey, data);\n        }\n      }),\n    );\n  }\n}\n```\n\n---\n\n## 6. Template Optimization (MEDIUM)\n\n### Use New Control Flow Syntax\n\n```html\n\u003C!-- CORRECT - New control flow (faster, smaller bundle) -->\n@if (user()) {\n\u003Cspan>{{ user()!.name }}\u003C\u002Fspan>\n} @else {\n\u003Cspan>Guest\u003C\u002Fspan>\n} @for (item of items(); track item.id) {\n\u003Capp-item [item]=\"item\" \u002F>\n} @empty {\n\u003Cp>No items\u003C\u002Fp>\n}\n\n\u003C!-- WRONG - Legacy structural directives -->\n\u003Cspan *ngIf=\"user; else guest\">{{ user.name }}\u003C\u002Fspan>\n\u003Cng-template #guest>\u003Cspan>Guest\u003C\u002Fspan>\u003C\u002Fng-template>\n```\n\n### Avoid Complex Template Expressions\n\n```typescript\n\u002F\u002F CORRECT - Precompute in component\nclass Component {\n  items = signal\u003CItem[]>([]);\n  sortedItems = computed(() =>\n    [...this.items()].sort((a, b) => a.name.localeCompare(b.name))\n  );\n}\n\n\u002F\u002F Template\n@for (item of sortedItems(); track item.id) { ... }\n\n\u002F\u002F WRONG - Sorting in template every render\n@for (item of items() | sort:'name'; track item.id) { ... }\n```\n\n---\n\n## 7. State Management (MEDIUM)\n\n### Use Selectors to Prevent Re-renders\n\n```typescript\n\u002F\u002F CORRECT - Selective subscription\n@Component({\n  template: `\u003Cspan>{{ userName() }}\u003C\u002Fspan>`,\n})\nclass HeaderComponent {\n  private store = inject(Store);\n  \u002F\u002F Only re-renders when userName changes\n  userName = this.store.selectSignal(selectUserName);\n}\n\n\u002F\u002F WRONG - Subscribing to entire state\n@Component({\n  template: `\u003Cspan>{{ state().user.name }}\u003C\u002Fspan>`,\n})\nclass HeaderComponent {\n  private store = inject(Store);\n  \u002F\u002F Re-renders on ANY state change\n  state = toSignal(this.store);\n}\n```\n\n### Colocate State with Features\n\n```typescript\n\u002F\u002F CORRECT - Feature-scoped store\n@Injectable() \u002F\u002F NOT providedIn: 'root'\nexport class ProductStore { ... }\n\n@Component({\n  providers: [ProductStore], \u002F\u002F Scoped to component tree\n})\nexport class ProductPageComponent {\n  store = inject(ProductStore);\n}\n\n\u002F\u002F WRONG - Everything in global store\n@Injectable({ providedIn: 'root' })\nexport class GlobalStore {\n  \u002F\u002F Contains ALL app state - hard to tree-shake\n}\n```\n\n---\n\n## 8. Memory Management (LOW-MEDIUM)\n\n### Use takeUntilDestroyed for Subscriptions\n\n```typescript\nimport { takeUntilDestroyed } from '@angular\u002Fcore\u002Frxjs-interop';\n\n@Component({...})\nexport class DataComponent {\n  private destroyRef = inject(DestroyRef);\n\n  constructor() {\n    this.data$.pipe(\n      takeUntilDestroyed(this.destroyRef)\n    ).subscribe(data => this.process(data));\n  }\n}\n\n\u002F\u002F WRONG - Manual subscription management\nexport class DataComponent implements OnDestroy {\n  private subscription!: Subscription;\n\n  ngOnInit() {\n    this.subscription = this.data$.subscribe(...);\n  }\n\n  ngOnDestroy() {\n    this.subscription.unsubscribe(); \u002F\u002F Easy to forget\n  }\n}\n```\n\n### Prefer Signals Over Subscriptions\n\n```typescript\n\u002F\u002F CORRECT - No subscription needed\n@Component({\n  template: `\u003Cdiv>{{ data().name }}\u003C\u002Fdiv>`,\n})\nexport class Component {\n  data = toSignal(this.service.data$, { initialValue: null });\n}\n\n\u002F\u002F WRONG - Manual subscription\n@Component({\n  template: `\u003Cdiv>{{ data?.name }}\u003C\u002Fdiv>`,\n})\nexport class Component implements OnInit, OnDestroy {\n  data: Data | null = null;\n  private sub!: Subscription;\n\n  ngOnInit() {\n    this.sub = this.service.data$.subscribe((d) => (this.data = d));\n  }\n\n  ngOnDestroy() {\n    this.sub.unsubscribe();\n  }\n}\n```\n\n---\n\n## Quick Reference Checklist\n\n### New Component\n\n- [ ] `changeDetection: ChangeDetectionStrategy.OnPush`\n- [ ] `standalone: true`\n- [ ] Signals for state (`signal()`, `input()`, `output()`)\n- [ ] `inject()` for dependencies\n- [ ] `@for` with `track` expression\n\n### Performance Review\n\n- [ ] No methods in templates (use pipes or computed)\n- [ ] Large lists virtualized\n- [ ] Heavy components deferred\n- [ ] Routes lazy-loaded\n- [ ] Third-party libs dynamically imported\n\n### SSR Check\n\n- [ ] Hydration configured\n- [ ] Critical content renders first\n- [ ] Non-critical content uses `@defer (hydrate on ...)`\n- [ ] TransferState for server-fetched data\n\n---\n\n## Resources\n\n- [Angular Performance Guide](https:\u002F\u002Fangular.dev\u002Fbest-practices\u002Fperformance)\n- [Zoneless Angular](https:\u002F\u002Fangular.dev\u002Fguide\u002Fexperimental\u002Fzoneless)\n- [Angular SSR Guide](https:\u002F\u002Fangular.dev\u002Fguide\u002Fssr)\n- [Change Detection Deep Dive](https:\u002F\u002Fangular.dev\u002Fguide\u002Fchange-detection)\n","# Angular Best Practices\n\nComprehensive performance optimization guide for Angular applications. Contains prioritized rules for eliminating performance bottlenecks, optimizing bundles, and improving rendering.\n\n## When to Apply\n\nReference these guidelines when:\n\n- Writing new Angular components or pages\n- Implementing data fetching patterns\n- Reviewing code for performance issues\n- Refactoring existing Angular code\n- Optimizing bundle size or load times\n- Configuring SSR\u002Fhydration\n\n---\n\n## Rule Categories by Priority\n\n| Priority | Category              | Impact     | Focus                           |\n| -------- | --------------------- | ---------- | ------------------------------- |\n| 1        | Change Detection      | CRITICAL   | Signals, OnPush, Zoneless       |\n| 2        | Async Waterfalls      | CRITICAL   | RxJS patterns, SSR preloading   |\n| 3        | Bundle Optimization   | CRITICAL   | Lazy loading, tree shaking      |\n| 4        | Rendering Performance | HIGH       | @defer, trackBy, virtualization |\n| 5        | Server-Side Rendering | HIGH       | Hydration, prerendering         |\n| 6        | Template Optimization | MEDIUM     | Control flow, pipes             |\n| 7        | State Management      | MEDIUM     | Signal patterns, selectors      |\n| 8        | Memory Management     | LOW-MEDIUM | Cleanup, subscriptions          |\n\n---\n\n## 1. Change Detection (CRITICAL)\n\n### Use OnPush Change Detection\n\n```typescript\n\u002F\u002F CORRECT - OnPush with Signals\n@Component({\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  template: `\u003Cdiv>{{ count() }}\u003C\u002Fdiv>`,\n})\nexport class CounterComponent {\n  count = signal(0);\n}\n\n\u002F\u002F WRONG - Default change detection\n@Component({\n  template: `\u003Cdiv>{{ count }}\u003C\u002Fdiv>`, \u002F\u002F Checked every cycle\n})\nexport class CounterComponent {\n  count = 0;\n}\n```\n\n### Prefer Signals Over Mutable Properties\n\n```typescript\n\u002F\u002F CORRECT - Signals trigger precise updates\n@Component({\n  template: `\n    \u003Ch1>{{ title() }}\u003C\u002Fh1>\n    \u003Cp>Count: {{ count() }}\u003C\u002Fp>\n  `,\n})\nexport class DashboardComponent {\n  title = signal(\"Dashboard\");\n  count = signal(0);\n}\n\n\u002F\u002F WRONG - Mutable properties require zone.js checks\n@Component({\n  template: `\n    \u003Ch1>{{ title }}\u003C\u002Fh1>\n    \u003Cp>Count: {{ count }}\u003C\u002Fp>\n  `,\n})\nexport class DashboardComponent {\n  title = \"Dashboard\";\n  count = 0;\n}\n```\n\n### Enable Zoneless for New Projects\n\n```typescript\n\u002F\u002F main.ts - Zoneless Angular (v20+)\nbootstrapApplication(AppComponent, {\n  providers: [provideZonelessChangeDetection()],\n});\n```\n\n**Benefits:**\n\n- No zone.js patches on async APIs\n- Smaller bundle (~15KB savings)\n- Clean stack traces for debugging\n- Better micro-frontend compatibility\n\n---\n\n## 2. Async Operations & Waterfalls (CRITICAL)\n\n### Eliminate Sequential Data Fetching\n\n```typescript\n\u002F\u002F WRONG - Nested subscriptions create waterfalls\nthis.route.params.subscribe((params) => {\n  \u002F\u002F 1. Wait for params\n  this.userService.getUser(params.id).subscribe((user) => {\n    \u002F\u002F 2. Wait for user\n    this.postsService.getPosts(user.id).subscribe((posts) => {\n      \u002F\u002F 3. Wait for posts\n    });\n  });\n});\n\n\u002F\u002F CORRECT - Parallel execution with forkJoin\nforkJoin({\n  user: this.userService.getUser(id),\n  posts: this.postsService.getPosts(id),\n}).subscribe((data) => {\n  \u002F\u002F Fetched in parallel\n});\n\n\u002F\u002F CORRECT - Flatten dependent calls with switchMap\nthis.route.params\n  .pipe(\n    map((p) => p.id),\n    switchMap((id) => this.userService.getUser(id)),\n  )\n  .subscribe();\n```\n\n### Avoid Client-Side Waterfalls in SSR\n\n```typescript\n\u002F\u002F CORRECT - Use resolvers or blocking hydration for critical data\nexport const route: Route = {\n  path: \"profile\u002F:id\",\n  resolve: { data: profileResolver }, \u002F\u002F Fetched on server before navigation\n  component: ProfileComponent,\n};\n\n\u002F\u002F WRONG - Component fetches data on init\nclass ProfileComponent implements OnInit {\n  ngOnInit() {\n    \u002F\u002F Starts ONLY after JS loads and component renders\n    this.http.get(\"\u002Fapi\u002Fprofile\").subscribe();\n  }\n}\n```\n\n---\n\n## 3. Bundle Optimization (CRITICAL)\n\n### Lazy Load Routes\n\n```typescript\n\u002F\u002F CORRECT - Lazy load feature routes\nexport const routes: Routes = [\n  {\n    path: \"admin\",\n    loadChildren: () =>\n      import(\".\u002Fadmin\u002Fadmin.routes\").then((m) => m.ADMIN_ROUTES),\n  },\n  {\n    path: \"dashboard\",\n    loadComponent: () =>\n      import(\".\u002Fdashboard\u002Fdashboard.component\").then(\n        (m) => m.DashboardComponent,\n      ),\n  },\n];\n\n\u002F\u002F WRONG - Eager loading everything\nimport { AdminModule } from \".\u002Fadmin\u002Fadmin.module\";\nexport const routes: Routes = [\n  { path: \"admin\", component: AdminComponent }, \u002F\u002F In main bundle\n];\n```\n\n### Use @defer for Heavy Components\n\n```html\n\u003C!-- CORRECT - Heavy component loads on demand -->\n@defer (on viewport) {\n\u003Capp-analytics-chart [data]=\"data()\" \u002F>\n} @placeholder {\n\u003Cdiv class=\"chart-skeleton\">\u003C\u002Fdiv>\n}\n\n\u003C!-- WRONG - Heavy component in initial bundle -->\n\u003Capp-analytics-chart [data]=\"data()\" \u002F>\n```\n\n### Avoid Barrel File Re-exports\n\n```typescript\n\u002F\u002F WRONG - Imports entire barrel, breaks tree-shaking\nimport { Button, Modal, Table } from \"@shared\u002Fcomponents\";\n\n\u002F\u002F CORRECT - Direct imports\nimport { Button } from \"@shared\u002Fcomponents\u002Fbutton\u002Fbutton.component\";\nimport { Modal } from \"@shared\u002Fcomponents\u002Fmodal\u002Fmodal.component\";\n```\n\n### Dynamic Import Third-Party Libraries\n\n```typescript\n\u002F\u002F CORRECT - Load heavy library on demand\nasync loadChart() {\n  const { Chart } = await import('chart.js');\n  this.chart = new Chart(this.canvas, config);\n}\n\n\u002F\u002F WRONG - Bundle Chart.js in main chunk\nimport { Chart } from 'chart.js';\n```\n\n---\n\n## 4. Rendering Performance (HIGH)\n\n### Always Use trackBy with @for\n\n```html\n\u003C!-- CORRECT - Efficient DOM updates -->\n@for (item of items(); track item.id) {\n\u003Capp-item-card [item]=\"item\" \u002F>\n}\n\n\u003C!-- WRONG - Entire list re-renders on any change -->\n@for (item of items(); track $index) {\n\u003Capp-item-card [item]=\"item\" \u002F>\n}\n```\n\n### Use Virtual Scrolling for Large Lists\n\n```typescript\nimport { CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll } from '@angular\u002Fcdk\u002Fscrolling';\n\n@Component({\n  imports: [CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll],\n  template: `\n    \u003Ccdk-virtual-scroll-viewport itemSize=\"50\" class=\"viewport\">\n      \u003Cdiv *cdkVirtualFor=\"let item of items\" class=\"item\">\n        {{ item.name }}\n      \u003C\u002Fdiv>\n    \u003C\u002Fcdk-virtual-scroll-viewport>\n  `\n})\n```\n\n### Prefer Pure Pipes Over Methods\n\n```typescript\n\u002F\u002F CORRECT - Pure pipe, memoized\n@Pipe({ name: 'filterActive', standalone: true, pure: true })\nexport class FilterActivePipe implements PipeTransform {\n  transform(items: Item[]): Item[] {\n    return items.filter(i => i.active);\n  }\n}\n\n\u002F\u002F Template\n@for (item of items() | filterActive; track item.id) { ... }\n\n\u002F\u002F WRONG - Method called every change detection\n@for (item of getActiveItems(); track item.id) { ... }\n```\n\n### Use computed() for Derived Data\n\n```typescript\n\u002F\u002F CORRECT - Computed, cached until dependencies change\nexport class ProductStore {\n  products = signal\u003CProduct[]>([]);\n  filter = signal('');\n\n  filteredProducts = computed(() => {\n    const f = this.filter().toLowerCase();\n    return this.products().filter(p =>\n      p.name.toLowerCase().includes(f)\n    );\n  });\n}\n\n\u002F\u002F WRONG - Recalculates every access\nget filteredProducts() {\n  return this.products.filter(p =>\n    p.name.toLowerCase().includes(this.filter)\n  );\n}\n```\n\n---\n\n## 5. Server-Side Rendering (HIGH)\n\n### Configure Incremental Hydration\n\n```typescript\n\u002F\u002F app.config.ts\nimport {\n  provideClientHydration,\n  withIncrementalHydration,\n} from \"@angular\u002Fplatform-browser\";\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideClientHydration(withIncrementalHydration(), withEventReplay()),\n  ],\n};\n```\n\n### Defer Non-Critical Content\n\n```html\n\u003C!-- Critical above-the-fold content -->\n\u003Capp-header \u002F>\n\u003Capp-hero \u002F>\n\n\u003C!-- Below-fold deferred with hydration triggers -->\n@defer (hydrate on viewport) {\n\u003Capp-product-grid \u002F>\n} @defer (hydrate on interaction) {\n\u003Capp-chat-widget \u002F>\n}\n```\n\n### Use TransferState for SSR Data\n\n```typescript\n@Injectable({ providedIn: \"root\" })\nexport class DataService {\n  private http = inject(HttpClient);\n  private transferState = inject(TransferState);\n  private platformId = inject(PLATFORM_ID);\n\n  getData(key: string): Observable\u003CData> {\n    const stateKey = makeStateKey\u003CData>(key);\n\n    if (isPlatformBrowser(this.platformId)) {\n      const cached = this.transferState.get(stateKey, null);\n      if (cached) {\n        this.transferState.remove(stateKey);\n        return of(cached);\n      }\n    }\n\n    return this.http.get\u003CData>(`\u002Fapi\u002F${key}`).pipe(\n      tap((data) => {\n        if (isPlatformServer(this.platformId)) {\n          this.transferState.set(stateKey, data);\n        }\n      }),\n    );\n  }\n}\n```\n\n---\n\n## 6. Template Optimization (MEDIUM)\n\n### Use New Control Flow Syntax\n\n```html\n\u003C!-- CORRECT - New control flow (faster, smaller bundle) -->\n@if (user()) {\n\u003Cspan>{{ user()!.name }}\u003C\u002Fspan>\n} @else {\n\u003Cspan>Guest\u003C\u002Fspan>\n} @for (item of items(); track item.id) {\n\u003Capp-item [item]=\"item\" \u002F>\n} @empty {\n\u003Cp>No items\u003C\u002Fp>\n}\n\n\u003C!-- WRONG - Legacy structural directives -->\n\u003Cspan *ngIf=\"user; else guest\">{{ user.name }}\u003C\u002Fspan>\n\u003Cng-template #guest>\u003Cspan>Guest\u003C\u002Fspan>\u003C\u002Fng-template>\n```\n\n### Avoid Complex Template Expressions\n\n```typescript\n\u002F\u002F CORRECT - Precompute in component\nclass Component {\n  items = signal\u003CItem[]>([]);\n  sortedItems = computed(() =>\n    [...this.items()].sort((a, b) => a.name.localeCompare(b.name))\n  );\n}\n\n\u002F\u002F Template\n@for (item of sortedItems(); track item.id) { ... }\n\n\u002F\u002F WRONG - Sorting in template every render\n@for (item of items() | sort:'name'; track item.id) { ... }\n```\n\n---\n\n## 7. State Management (MEDIUM)\n\n### Use Selectors to Prevent Re-renders\n\n```typescript\n\u002F\u002F CORRECT - Selective subscription\n@Component({\n  template: `\u003Cspan>{{ userName() }}\u003C\u002Fspan>`,\n})\nclass HeaderComponent {\n  private store = inject(Store);\n  \u002F\u002F Only re-renders when userName changes\n  userName = this.store.selectSignal(selectUserName);\n}\n\n\u002F\u002F WRONG - Subscribing to entire state\n@Component({\n  template: `\u003Cspan>{{ state().user.name }}\u003C\u002Fspan>`,\n})\nclass HeaderComponent {\n  private store = inject(Store);\n  \u002F\u002F Re-renders on ANY state change\n  state = toSignal(this.store);\n}\n```\n\n### Colocate State with Features\n\n```typescript\n\u002F\u002F CORRECT - Feature-scoped store\n@Injectable() \u002F\u002F NOT providedIn: 'root'\nexport class ProductStore { ... }\n\n@Component({\n  providers: [ProductStore], \u002F\u002F Scoped to component tree\n})\nexport class ProductPageComponent {\n  store = inject(ProductStore);\n}\n\n\u002F\u002F WRONG - Everything in global store\n@Injectable({ providedIn: 'root' })\nexport class GlobalStore {\n  \u002F\u002F Contains ALL app state - hard to tree-shake\n}\n```\n\n---\n\n## 8. Memory Management (LOW-MEDIUM)\n\n### Use takeUntilDestroyed for Subscriptions\n\n```typescript\nimport { takeUntilDestroyed } from '@angular\u002Fcore\u002Frxjs-interop';\n\n@Component({...})\nexport class DataComponent {\n  private destroyRef = inject(DestroyRef);\n\n  constructor() {\n    this.data$.pipe(\n      takeUntilDestroyed(this.destroyRef)\n    ).subscribe(data => this.process(data));\n  }\n}\n\n\u002F\u002F WRONG - Manual subscription management\nexport class DataComponent implements OnDestroy {\n  private subscription!: Subscription;\n\n  ngOnInit() {\n    this.subscription = this.data$.subscribe(...);\n  }\n\n  ngOnDestroy() {\n    this.subscription.unsubscribe(); \u002F\u002F Easy to forget\n  }\n}\n```\n\n### Prefer Signals Over Subscriptions\n\n```typescript\n\u002F\u002F CORRECT - No subscription needed\n@Component({\n  template: `\u003Cdiv>{{ data().name }}\u003C\u002Fdiv>`,\n})\nexport class Component {\n  data = toSignal(this.service.data$, { initialValue: null });\n}\n\n\u002F\u002F WRONG - Manual subscription\n@Component({\n  template: `\u003Cdiv>{{ data?.name }}\u003C\u002Fdiv>`,\n})\nexport class Component implements OnInit, OnDestroy {\n  data: Data | null = null;\n  private sub!: Subscription;\n\n  ngOnInit() {\n    this.sub = this.service.data$.subscribe((d) => (this.data = d));\n  }\n\n  ngOnDestroy() {\n    this.sub.unsubscribe();\n  }\n}\n```\n\n---\n\n## Quick Reference Checklist\n\n### New Component\n\n- [ ] `changeDetection: ChangeDetectionStrategy.OnPush`\n- [ ] `standalone: true`\n- [ ] Signals for state (`signal()`, `input()`, `output()`)\n- [ ] `inject()` for dependencies\n- [ ] `@for` with `track` expression\n\n### Performance Review\n\n- [ ] No methods in templates (use pipes or computed)\n- [ ] Large lists virtualized\n- [ ] Heavy components deferred\n- [ ] Routes lazy-loaded\n- [ ] Third-party libs dynamically imported\n\n### SSR Check\n\n- [ ] Hydration configured\n- [ ] Critical content renders first\n- [ ] Non-critical content uses `@defer (hydrate on ...)`\n- [ ] TransferState for server-fetched data\n\n---\n\n## Resources\n\n- [Angular Performance Guide](https:\u002F\u002Fangular.dev\u002Fbest-practices\u002Fperformance)\n- [Zoneless Angular](https:\u002F\u002Fangular.dev\u002Fguide\u002Fexperimental\u002Fzoneless)\n- [Angular SSR Guide](https:\u002F\u002Fangular.dev\u002Fguide\u002Fssr)\n- [Change Detection Deep Dive](https:\u002F\u002Fangular.dev\u002Fguide\u002Fchange-detection)\n",{"name":5,"risk":36,"source":27,"description":7},"main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Faiskillstore\u002Fmarketplace\u002Fsickn33\u002Fangular-best-practices",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":52,"prompt":62,"zipAvailable":63},[53,56,59],{"tool":54,"command":55},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill angular-best-practices",{"tool":57,"command":58},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill angular-best-practices",{"tool":60,"command":61},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill angular-best-practices","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fsickn33\u002Fangular-best-practices\u002FSKILL.md",true,[65,71,75],{"path":66,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":69,"sourceHtmlUrl":70},"metadata.json","REFERENCE","参考文档",724,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fsickn33\u002Fangular-best-practices\u002Fmetadata.json",{"path":72,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":73,"sourceHtmlUrl":74},"README.md",1845,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fsickn33\u002Fangular-best-practices\u002FREADME.md",{"path":76,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":77,"sourceHtmlUrl":78},"skill-report.json",9826,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fsickn33\u002Fangular-best-practices\u002Fskill-report.json",[80,94,104,113,119,126],{"skillNo":81,"name":82,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":90,"publishedAt":37},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[85],{"categoryNo":86,"categoryName":87,"taxonomyType":88,"taxonomyTypeDesc":89},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[91,92,93],"model","model hierarchy","model hierarchy skill",{"skillNo":95,"name":96,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":97,"categories":98,"keywords":100,"publishedAt":37},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[99],{"categoryNo":86,"categoryName":87,"taxonomyType":88,"taxonomyTypeDesc":89},[101,102,103],"MCP","zoom","zoom mcp",{"skillNo":105,"name":106,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":107,"categories":108,"keywords":109,"publishedAt":37},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[110,111,112,102],"sdk","zoom apps sdk","apps",{"skillNo":114,"name":115,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":116,"categories":117,"keywords":118,"publishedAt":37},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[115],{"skillNo":120,"name":121,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":122,"categories":123,"keywords":125,"publishedAt":37},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[124],{"categoryNo":86,"categoryName":87,"taxonomyType":88,"taxonomyTypeDesc":89},[121],{"skillNo":127,"name":128,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":129,"categories":130,"keywords":131,"publishedAt":37},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[128]]