· frameworks · 6 min read
10 Angular Tips You Didn't Know You Needed
Discover 10 lesser-known Angular tips - from OnPush and trackBy to ngZone tricks, typed reactive forms, and standalone components - that will make your apps faster, safer, and easier to maintain.

Why these tips matter
Angular is powerful, but some of the best improvements come from small, focused patterns and APIs many teams overlook. The tips below are practical, immediately actionable, and cover performance, maintainability, and developer ergonomics. Each tip includes short code examples and links to the official docs for deeper reading.
Tip 1 - Use trackBy with *ngFor to prevent unnecessary DOM churn
When rendering lists, Angular by default tracks items by object identity. For lists where items are re-created or its array is replaced often, use trackBy
to give Angular a stable key and avoid re-creating DOM nodes.
Why it helps: reduces DOM operations, improves performance especially for large lists.
Example:
<li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
trackById(index: number, item: { id: string }) {
return item.id; // or some stable unique value
}
Docs: https://angular.io/api/common/NgForOf#trackBy
Tip 2 - Prefer OnPush change detection and immutable updates
Switching a component to ChangeDetectionStrategy.OnPush
and using immutable updates (returning new objects/arrays instead of mutating) can massively reduce change detection work.
Why it helps: limits checks to when @Input references change or when events/observables emit, reducing CPU work in large UIs.
Example:
@Component({
// ...
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserListComponent {
@Input() users: ReadonlyArray<User> = [];
}
// Instead of users.push(newUser) do:
this.users = [...this.users, newUser];
Docs: https://angular.io/guide/change-detection
When not to use: small one-off components, simple apps, or when you need frequent deep mutation and don’t want to convert to immutable patterns.
Tip 3 - Let the async pipe manage subscriptions and lifecycle
Using the AsyncPipe
in templates automatically subscribes and unsubscribes for you - avoiding leaks and copying less boilerplate code.
Why it helps: prevents subscription leaks, clarifies declarative data flow.
Example:
<div *ngIf="users$ | async as users">
<app-user *ngFor="let u of users" [user]="u"></app-user>
</div>
In components, avoid manual subscription in many cases. When you must subscribe in TS, always unsubscribe (or use takeUntil
, firstValueFrom
, etc.).
Docs: https://angular.io/api/common/AsyncPipe
Tip 4 - Run heavy non-Angular work outside Angular with NgZone
If you’re doing expensive non-DOM work or high-frequency events (e.g., canvas drawing, heavy computations, some scroll/resize handlers), run them outside Angular’s zone to avoid triggering change detection on each event.
Why it helps: prevents unnecessary change detection cycles and keeps UI responsive.
Example:
constructor(private ngZone: NgZone) {}
ngAfterViewInit() {
this.ngZone.runOutsideAngular(() => {
window.addEventListener('scroll', this.onScroll);
});
}
onScroll = (ev: Event) => {
// heavy logic or throttled work
// when you need to update Angular-bound state, re-enter zone:
// this.ngZone.run(() => this.counter++);
}
Docs: https://angular.io/api/core/NgZone
Tip 5 - Prefer Renderer2 over direct DOM manipulation for portability/SSR
Accessing DOM via document
or elementRef.nativeElement
couples code to the browser and hurts SSR and web worker compatibility. Use Renderer2
for DOM operations to keep things portable and testable.
Example:
constructor(private renderer: Renderer2, private el: ElementRef) {}
ngAfterViewInit() {
const div = this.renderer.createElement('div');
this.renderer.setStyle(div, 'color', 'tomato');
this.renderer.appendChild(this.el.nativeElement, div);
}
Docs: https://angular.io/api/core/Renderer2
Tip 6 - Use providedIn: 'any'
when you actually need per-lazy-module service instances
Angular’s providedIn: 'root'
produces a singleton service. But if you want an instance per lazy-loaded module (isolated service state), providedIn: 'any'
can give each lazy module its own instance (useful for feature module scoping).
Example:
@Injectable({ providedIn: 'any' })
export class FeatureStateService {
/* per-lazy-module state */
}
When to use: stateful services used inside lazy-loaded modules where isolation is desired. If you want a single global instance, keep providedIn: 'root'
.
Docs: https://angular.io/guide/providers
Tip 7 - Embrace typed reactive forms (and enable strict mode)
Recent Angular versions added typed reactive forms. Combined with TypeScript strict
/ Angular strict templates, you get compile-time safety for form values and fewer runtime surprises.
Why it helps: safer refactors, clear contracts for form value shapes.
Example (simplified):
const fb = new FormBuilder();
const profileForm = fb.group<{
name: FormControl<string | null>;
age: FormControl<number | null>;
}>({
name: fb.control('', { nonNullable: true }),
age: fb.control(null),
});
const val: { name: string; age: number | null } = profileForm.value;
Docs: see Angular’s guides on forms and strict mode: https://angular.io/guide/strict-mode
Tip 8 - Use route resolvers and smart preloading for better perceived performance
Load critical data before a route activates with resolvers so the component receives needed data immediately. For lazy modules you can configure preloading strategies (e.g., PreloadAllModules
or custom strategies) to improve navigation speed without heavy initial load.
Example resolver wiring:
// resolver implements Resolve<T>
{ path: 'profile', component: ProfileComponent, resolve: { user: UserResolver } }
Preloading example:
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules });
Docs: https://angular.io/guide/router
When to use: apps with many lazy routes and need quick navigation while keeping initial bundle small.
Tip 9 - Use Angular DevTools and the Change Detection profiler
Angular DevTools is a Chrome extension that lets you inspect component trees, input changes, and measure change detection costs. Before optimizing, profile to find hotspots instead of guessing.
Why it helps: targeted perf fixes, avoids wasted effort.
Install and learn: https://angular.io/guide/devtools
Quick wins from the profiler: find components with frequent change detection, then consider OnPush, memoization, or moving heavy work out of Angular’s zone.
Tip 10 - Try standalone components & the reactivity model (Signals)
Standalone components reduce boilerplate from NgModules and simplify component trees, especially in new projects. Angular’s reactivity model (signals/reactive primitives introduced in recent Angular versions) lets you create local reactive state that’s simpler and often more efficient than complex RxJS setups for UI state.
Standalone example:
@Component({
standalone: true,
imports: [CommonModule],
selector: 'app-hello',
template: `Hi {{ name() }}`,
})
export class HelloComponent {
name = signal('Angular');
}
Docs: standalone components: https://angular.io/guide/standalone-components Reactivity & signals: https://angular.io/guide/reactivity
When to migrate: new features and isolated components are excellent candidates. For large existing apps, adopt incrementally.
Quick checklist to adopt these tips
- Add
trackBy
on lists where items change often. - Start converting performance-sensitive components to
OnPush
and adopt immutable updates. - Replace manual subscriptions in templates with the
async
pipe. - Wrap heavy event listeners or loops with
ngZone.runOutsideAngular
. - Replace direct DOM manipulation with
Renderer2
where portability matters. - Review service scope and use
providedIn: 'any'
only where per-module instances are required. - Enable strict mode and experiment with typed reactive forms for safer forms.
- Introduce resolvers/preloading for smoother routing UX.
- Use Angular DevTools to prioritize optimizations.
- Try standalone components and signals for new code paths.
Final thoughts
These patterns are small changes with outsized impact. Start by profiling your app, pick one or two recommendations, and evaluate the impact. Over time, these practices will make your codebase faster, safer, and easier to maintain.
References
- Angular docs: https://angular.io
- Change detection guide: https://angular.io/guide/change-detection
- NgFor trackBy: https://angular.io/api/common/NgForOf#trackBy
- AsyncPipe: https://angular.io/api/common/AsyncPipe
- NgZone: https://angular.io/api/core/NgZone
- Renderer2: https://angular.io/api/core/Renderer2
- Providers guide: https://angular.io/guide/providers
- Strict mode: https://angular.io/guide/strict-mode
- Router guide: https://angular.io/guide/router
- DevTools: https://angular.io/guide/devtools
- Standalone components: https://angular.io/guide/standalone-components
- Reactivity & signals: https://angular.io/guide/reactivity