10 best practices in Angular that will transform your code quality
Angular has changed a lot in recent years (standalone, signals, new control flow). Even so, most of the problems we see in audits don’t come from a “lack of features,” but from small, repeated decisions that degrade quality: loose typing, runaway change detection, complex RxJS, routes without lazy loading, lack of useful tests…
In this direct and actionable guide we’ve gathered 10 practices that apply whether you’re starting a new project or maintaining a “long-lived” one. You’ll see what to do, why, and how with short examples. At the end, you’ll find a printable checklist so the team can adopt these improvements without friction.
Who it’s for
Tech leads, devs, and product managers looking for more maintainable code, predictable performance, and fewer surprises in production.
Quick index
- Start with strict mode and linters
- Use standalone components and organize by features
- Enable
OnPushor signals for change detection - Strict typing in TypeScript and templates
- RxJS without pain: composition and cancellation
- Pragmatic state management (signals/light stores)
- Routing with lazy loading and domain separation
- Template performance (
trackBy,@for, images) - Useful tests (unit, harnesses, light integration)
- Security, accessibility, and observability from day 1
1) Start with strict mode and linters
Consistency prevents technical debt.
Minimum checklist:
-
ng new app --strict(or enablestrictintsconfig.json).- ESLint + Angular rules; Prettier for formatting only.
- NPM scripts:
lint,typecheck,test,build. pre-commithooks to prevent code smells.
{
"scripts": {
"lint": "ng lint",
"typecheck": "tsc --noEmit",
"test": "ng test --watch=false"
}
}
Result: fewer surprises in production and faster PRs.
2) Use standalone components and organize by features
Standalone reduces boilerplate and simplifies dependencies. Organize the app by domains (features), not by file type.
src/app/
core/ # cross-cutting (interceptors, guards)
shared/ # reusable UI
features/
orders/
pages/ components/ data-access/ utils/
@Component({ selector:'app-order-list', standalone:true, template:`...` })
export class OrderListComponent {}
Result: scalability and simpler testing.
3) Enable OnPush or signals for change detection
Reduce unnecessary renders with ChangeDetectionStrategy.OnPush or adopt signals for a precise reactive model.
@Component({ changeDetection: ChangeDetectionStrategy.OnPush })
export class CardComponent { /* immutable inputs */ }
const subtotal = signal(100);
const vat = signal(0.21);
const total = computed(() => subtotal() * (1 + vat()));
Result: fewer bugs, better autocomplete, and safer refactors.
4) Strict typing in TypeScript and templates
Let the compiler protect you.
- Avoid
any: use discriminated types and utilities (Pick,Omit). - Make optionals/loading states explicit.
- In templates, use
input.required<T>()and typed outputs.
export type Order = Readonly<{ id:string; total:number; status:'pending'|'paid' }>
Result: fewer bugs, better autocomplete, and safer refactors.
5) RxJS without pain: composition and cancellation
Not everything has to be an Observable, but when you use it, do it right.
- Prefer
switchMap,combineLatest,withLatestFrom. - Use
takeUntilDestroyed()for auto-unsubscribe. - In templates, use the
asyncpipe or signals.
this.total$ = timer(0,60000).pipe(
switchMap(() => this.http.get<{total:number}>('/api/metrics')),
map(r => r.total)
);
Result: readable flows without memory leaks.
6) Pragmatic state management (signals/light stores)
Choose the tool based on complexity, not fashion.
- Local/simple: signals +
computed. - Shared: signal store or a service with signals.
- Critical/enterprise: NgRx when you need auditing, undo/redo, normalization.
@Injectable({providedIn:'root'})
export class OrdersStore {
private _orders = signal<Order[]>([]);
readonly total = computed(() => this._orders().reduce((a,o)=>a+o.total,0));
set(v:Order[]){ this._orders.set(v); }
}
Result: less friction and sufficient traceability.
7) Routing with lazy loading and domain separation
Load what’s needed, when it’s needed.
- Feature-based routes and lazy by default.
- Avoid mega-modules; leverage standalone in routes.
export const routes: Routes = [
{ path: 'orders', loadComponent: () => import('./features/orders/pages/orders.page').then(m => m.OrdersPage) }
];
Result: lower Time-to-Interactive and better Core Web Vitals.
8) Template performance (trackBy, @for, images)
Avoid full list re-renders and mind your assets.
- Use
@for (item of items; track item.id)or*ngFor trackBy. - Lazy-load images and assets (
loading="lazy"). - Consider
NgOptimizedImageforsrcsetand sizes.
Result: large lists and catalogs that fly.
9) Useful tests (unit, harnesses, light integration)
Don’t chase 100% coverage; chase confidence.
- Unit: pure logic and pipes.
- Component: Angular Testing Library or Harnesses.
- Light integration: key routes/guards/interceptors.
- E2E where it brings business value (happy path + critical).
Result: fast refactors with a real safety net.
10) Security, accessibility, and observability from day 1
Quality also means protecting, including, and measuring.
- Security:
HttpInterceptorfor headers, HTML sanitization, Content Security Policy. - Accessibility (a11y): ARIA roles, managed focus, contrast, keyboard navigation.
- Observability: structured logs, UX metrics (CLS, LCP), error boundary with
ErrorHandler.
Result: fewer incidents and a consistent experience.
Want to bring this into your project?
At TechsBCN we help teams modernize Angular: quality audits, migration guidance to standalone & signals, performance clinics, and testing playbooks. Let’s talk to review your code and prioritize impact in 2 weeks.
