| name | angular-best-practices |
| type | reference |
| description | Provides Angular best practices for components, modules, services, and reactive patterns. Use when working with Angular TypeScript files, component templates, NgModules, RxJS observables, or when the user mentions Angular, ng, or Angular CLI. |
| paths | ["**/*.component.ts","**/*.service.ts","**/*.module.ts","**/angular.json"] |
| effort | 3 |
| allowed-tools | Read, Glob, Grep, Write, Edit, Bash |
| user-invocable | true |
| when_to_use | When building Angular applications or working with RxJS streams |
Angular Best Practices
Critical rules (non-obvious)
- Always unsubscribe from Observables in
ngOnDestroy — use takeUntilDestroyed() (Angular 16+) or Subject + takeUntil
ChangeDetectionStrategy.OnPush: component only updates when input reference changes or async pipe emits — use for all leaf components
- Never mutate input objects/arrays: OnPush won't detect mutation; create new reference instead
trackBy is mandatory on *ngFor with dynamic lists — without it, every change re-renders all DOM nodes
async pipe auto-unsubscribes — prefer it over manual subscription in templates
Component with OnPush + signals (Angular 17+)
@Component({
selector: "app-product-list",
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@for (product of products(); track product.id) {
<app-product-card [product]="product" />
}
@if (loading()) { <app-spinner /> }
`,
})
export class ProductListComponent {
products = input.required<Product[]>();
loading = input(false);
total = computed(() => this.products().length);
}
Service with signals store pattern
@Injectable({ providedIn: "root" })
export class CartService {
_items = signal<[]>([]);
items = ..();
total = ( .().( sum + i. * i., ));
() {
..(
items.( i. === item.)
? items.( i. === item. ? { ...i, : i. + } : i)
: [...items, { ...item, : }]
);
}
}