Angular Components & Templates
Components are the building blocks of every Angular application: a TypeScript class holding state and logic, a template defining its view, and styles scoped to it. Modern Angular components are standalone (no NgModules required), use built-in control flow (@if, @for, @switch) instead of structural directives, lazy-load parts of the view with @defer, and increasingly rely on signals for inputs and state.
These changes, arriving across Angular 14–19 and now the defaults, make Angular components more concise, faster to render, and closer in feel to other modern frameworks, while keeping Angular's strengths: strong typing, dependency injection, and a batteries-included toolchain.
TL;DR
- Components are standalone by default: declare dependencies in the component's
importsarray. - Bindings:
{{ interpolation }},[property],(event),[(twoWay)],[class.x],[style.y]. - Use
@if,@for(with requiredtrack), and@switchfor control flow. @deferlazy-loads components on viewport, interaction, idle, or timer triggers, with placeholder and loading states.- Project content with
<ng-content>(optionallyselected slots). - Prefer
ChangeDetectionStrategy.OnPush(or zoneless), signal inputs, and small, focused components.
Quick Example
Using it, with a list and deferred reviews:
Core Concepts
Standalone Components
Since Angular 19, components are standalone by default: each lists the components, directives, and pipes it uses in imports. Applications bootstrap with bootstrapApplication(AppComponent, { providers: [...] }), and routes lazy-load components directly with loadComponent. NgModules still work for existing code, but new code doesn't need them.
Template Binding Syntax
Templates are type-checked in strict mode, which catches typos and wrong input types at build time.
Built-In Control Flow
@if (cond) { … } @else if (…) { … } @else { … }, withasaliases for narrowed values.@for (item of items; track item.id) { … } @empty { … }.trackis required, and it tells Angular how to identify items for efficient DOM updates. Contextual variables are$index,$first,$last,$even, and$count.@switch (value) { @case (x) { … } @default { … } }.
The built-in syntax replaced *ngIf, *ngFor, and ngSwitch: it's faster, needs no imports, and narrows types better. A schematic migrates existing templates.
Deferrable Views
@defer splits part of a template, and its dependencies, into a separate lazy chunk:
- Triggers:
on viewport,on interaction,on hover,on idle(default),on timer(2s),on immediate, orwhen condition. prefetch on …loads code before it renders.- Sub-blocks:
@placeholder,@loading(withminimumandaftertimings to prevent flicker), and@error. - With SSR, incremental hydration (
hydrate on …) delays hydrating deferred sections.
It's one of the easiest wins for initial load performance. See web performance.
Content Projection
<ng-content /> renders content passed between a component's tags, and select creates named slots (<ng-content select="[card-footer]" />). For more dynamic cases, ng-template with ngTemplateOutlet passes templates as inputs.
Lifecycle Hooks
ngOnInit (after the first input values), ngOnChanges (input changes, less needed with signal inputs), ngAfterViewInit (view children available), ngOnDestroy (cleanup), and the newer afterNextRender / afterRender for DOM work that must run after rendering. For cleanup, DestroyRef and takeUntilDestroyed() avoid manual unsubscribe logic.
Change Detection
With ChangeDetectionStrategy.OnPush, a component is checked only when its inputs change by reference, an event fires within it, a signal it reads changes, or it's explicitly marked. Combined with signals and immutable data, OnPush (or zoneless mode) dramatically reduces unnecessary work.
Component Design Patterns
- Presentational (dumb) components take inputs, emit outputs, and contain no data fetching, so they're reusable and easy to test.
- Container (smart) components inject services, load data, and coordinate state.
- Host bindings via the
hostmetadata object (preferred over@HostBinding/@HostListener) keep attributes, classes, and ARIA state on the component's element. - Directives add reusable behavior (tooltips, focus traps, permissions) to any element. Host directives compose them into components.
For shared UI libraries, Angular CDK and Angular Material provide accessible primitives (overlays, drag and drop, virtual scrolling, a11y utilities). See design systems.
Best Practices
Keep Components Small and Focused
One responsibility per component, with presentational components free of injected data services. Big "page" components should compose smaller ones.
Always Use track Wisely
Track by a stable unique ID, not by object identity or $index when items can be reordered or replaced. Good tracking prevents unnecessary DOM re-creation and lost component state.
Default to OnPush and Signals
New components should use signal inputs, computed for derived values, and OnPush or zoneless change detection. It's faster, and it forces clearer data flow.
Use NgOptimizedImage and @defer
NgOptimizedImage enforces image dimensions, lazy loading, and priority hints for LCP images, and @defer removes non-critical UI from the initial bundle. Both are low-effort, high-impact performance improvements.
Common Mistakes
Calling Expensive Methods in Templates
Use a computed signal or a pure pipe, which is memoized and recomputed only when inputs change.
Mutating Inputs Under OnPush
Pushing into an array input doesn't change its reference, so OnPush children don't update. Pass new arrays and objects, or use signals.
Forgetting to Import Dependencies in Standalone Components
Using a pipe or child component without adding it to imports causes template errors ("is not a known element"). The Angular language service and compiler point to the missing import.
FAQ
Do I still need NgModules in Angular?
No, not for new code. Standalone components, directives, and pipes declare their own dependencies, and applications bootstrap and lazy-load without modules. NgModules remain supported for existing apps and some libraries.
Why is track required in @for?
It tells Angular how to identify each item across renders, so it can move, add, or remove only the changed DOM nodes. Making it required prevents a common performance pitfall of the old *ngFor (missing trackBy), which re-created DOM unnecessarily.
What does @defer do?
It lazy-loads a section of a template, including its components, directives, and pipes, as a separate JavaScript chunk, rendering it when a trigger fires (in viewport, on interaction, idle, or timer). Placeholder and loading blocks handle the interim UI, which reduces initial bundle size and improves load performance.
When should I use OnPush change detection?
For essentially all new components. With signals and immutable data, OnPush avoids checking components whose inputs haven't changed. It's the natural companion to zoneless Angular.
Related Topics
- Angular — The framework overview
- Angular Signals — Reactive state and signal inputs
- Angular Dependency Injection — Providing services to components
- Angular Routing — Lazy-loaded route components
- Web Components — Angular Elements and interop
- Design Systems — Building shared component libraries