[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-130813eb-f4db-4989-a75b-5e7531f8800b":3,"$f2hJOsqfWSVrVD42lOcM3DjDp54YDtQ3Rq-LjD92al7c":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"130813eb-f4db-4989-a75b-5e7531f8800b","angular-best-practices","Angular性能优化和最佳实践指南。在编写、审查或重构Angular代码以实现最佳性能、包大小和渲染效率时使用。","cat_coding_frontend","mod_coding","sickn33,coding","---\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\ndate_added: \"2026-02-27\"\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 Use\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## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,182,744,"2026-05-16 13:02:52",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"编程开发","coding","mdi-code-braces","代码生成、调试、审查，提升开发效率",2,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"前端开发","frontend","mdi-language-html5","HTML\u002FCSS\u002FJavaScript\u002F框架相关",1,96,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"cbcb4011-bc2e-4f05-afb0-9c5ca775c01c","1.0.0","angular-best-practices.zip",6349,"uploads\u002Fskills\u002F130813eb-f4db-4989-a75b-5e7531f8800b\u002Fangular-best-practices.zip","54a5bcd9e3bfd28d866371f535c53a661aa4fd45235983bd46cc9243a63bba92","[{\"path\":\"README.md\",\"isDirectory\":false,\"size\":1845},{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":13438},{\"path\":\"metadata.json\",\"isDirectory\":false,\"size\":724}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]