Angular Best Practices
A closing checklist of habits that keep an Angular codebase fast, readable, and easy to change.
3 min read
You've now seen the pieces Angular apps are built from — components, binding, directives, services, signals, routing, forms. This lesson closes the course with the habits that separate a codebase that stays pleasant to work in from one that doesn't, as it grows past the size of a tutorial.
Keep components focused on presentation
A component's job is deciding what to render — logic like formatting, fetching, and calculation belongs in a service or a pipe, not sprinkled through template expressions or buried in a component method:
// Prefer this
@Component({ template: `<p>{{ total() | currency }}</p>` })
export class CartSummaryComponent {
private cart = inject(CartService);
total = computed(() => this.cart.items().reduce((sum, i) => sum + i.price, 0));
}<!-- over logic buried directly in the template -->
<p>{{ (cartItems | slice:0:cartItems.length).reduce(...) }}</p>Angular templates support enough expression syntax that it's tempting to compute things inline — resist it. A computed() signal or a service method is easier to test, easier to reuse, and easier to read at a glance than an expression embedded in markup.
Prefer signals and standalone components for new code
If you're starting fresh, default to standalone components (no NgModules), signals for component-local state, and @if/@for over *ngIf/*ngFor. These are Angular's current recommended defaults, get first-class tooling and compiler support, and mean less indirection between a template and the code backing it.
Type everything, including HTTP responses
interface Article {
id: number;
title: string;
}
getArticle(id: number): Observable<Article> {
return this.http.get<Article>(`/api/articles/${id}`);
}An untyped any from an HTTP call defeats the entire purpose of using TypeScript with Angular — every place that value flows through your app loses the compiler's ability to catch a typo or a shape mismatch before it reaches production.
Unsubscribe, or avoid subscribing manually at all
Prefer the async pipe over manual .subscribe() calls wherever a template can consume an observable directly — it handles unsubscription for you. When a manual subscription is unavoidable (inside a service, for instance), track it and clean it up in ngOnDestroy, or use RxJS's takeUntilDestroyed() operator, which does the same thing with less boilerplate.
Don't reach for OnPush or signals prematurely
Performance optimizations like ChangeDetectionStrategy.OnPush add real constraints (reference equality for inputs) in exchange for real benefits (fewer checks). Apply them where you've noticed an actual problem, not by default on every component — premature optimization here mostly adds subtle bugs without a measurable benefit.
Let the CLI generate the boilerplate
ng generate component, ng generate service, and friends produce consistently named, correctly wired files every time. Hand-writing that boilerplate invites small inconsistencies — a missing selector prefix, a forgotten @Injectable — that are easy to avoid by letting the tool do it.
Where to go from here
This course covered Angular's core mental model and the tools you'll use in nearly every app: components, binding, services, routing, and forms. From here, the fastest way to solidify it is building something real — a small app with a few routes, a form, and a call to a public API will exercise almost everything in this course at once.