Modern Angular Best Practices
A complete set of rules covering everything from TypeScript strictness to signal-based reactivity, SSR hydration, bundle optimization, and accessible UI — so every component, service, and route you ship is fast, tested, and maintainable.
TypeScript
- Use strict type checking with
strict: truein tsconfig - Avoid
any; useunknownwhen type is uncertain - Use
import typefor type-only imports - Add explicit return types to exported functions
- Prefer
readonlyfor data that should not be mutated
Components
- Use standalone components with
ChangeDetectionStrategy.OnPush - Use
input()andoutput()functions instead of@Input()/@Output()decorators - Use
inject()instead of constructor injection - Use
computed()for derived state — memoized, only recalculates when dependencies change - Keep components small and single-responsibility
- Prefer inline templates for small components
Templates
- Use
@if,@for,@switchinstead of*ngIf,*ngFor,*ngSwitch - Use
[class.active]bindings instead of[ngClass] - Use
@deferfor heavy below-fold content - Always provide
trackwith@forloops
Signals & Reactivity
- Use signals for local component state
- Use
computed()for derived state instead of getters - Use
effect()for side effects only — never for state synchronization - Use
toSignal()to bridge RxJS observables into signal-based templates
Performance
- Preload critical data with route resolvers — eliminate loading waterfalls
- Lazy-load routes and
@deferheavy views - Tree-shake imports — use standalone component imports, not full modules
- Avoid synchronous layout reads in loops — batch DOM operations
RxJS
- Unsubscribe from observables — use
takeUntilDestroyed()orasyncpipe - Place
catchErrorinsideswitchMapto keep the outer stream alive - Use
switchMapfor latest-only,exhaustMapfor ignore-while-busy
Testing
- Use component harnesses over direct DOM queries
- Mock services with
jasmine.createSpyObjorjest.fn() - Test signal state changes and template output, not implementation details
Architecture
- One feature per lazy-loaded route module
- Use facades when components orchestrate 3+ services with shared state
- Use environment-based configuration — no hardcoded URLs or API keys
Quick Reference
| Pattern | Use | Avoid |
|---|---|---|
| Signal inputs | input<T>() | @Input() |
| Signal outputs | output<T>() | @Output() |
| Dependency injection | inject() | Constructor injection |
| Control flow | @if, @for, @switch | *ngIf, *ngFor |
| Class binding | [class.active] | [ngClass] |
| Change detection | OnPush | Default |
| Derived state | computed() | Getters |
Optional Library Skills
Install library-specific rules alongside this core skill:
| Library | Skill Page |
|---|---|
| NgRx | angular-best-practices-ngrx |
| SignalStore | angular-best-practices-signalstore |
| TanStack Query | angular-best-practices-tanstack |
| Angular Material | angular-best-practices-material |
| PrimeNG | angular-best-practices-primeng |
| Spartan UI | angular-best-practices-spartan |
| Transloco | angular-best-practices-transloco |
Links
- GitHub Repository
- Submit a Rule via GitHub Issues
- Browse All Skills
License
MIT