Components & Templates
Component lifecycle, inputs/outputs, content projection, template syntax
Components are the fundamental building block of Angular applications. Each component encapsulates its template, styles, and logic. Understanding the component lifecycle, the host element, content projection, and the new signal-based reactivity model is essential for building efficient Angular UIs.
Key Points
- @Component decorator: selector, template/templateUrl, styles/styleUrls, standalone: true (Angular 17+)
- Component lifecycle: constructor → ngOnChanges → ngOnInit → ngDoCheck → ngAfterContentInit → ngAfterViewInit → ngOnDestroy
- ngOnInit: use for initialisation logic — constructor is for DI only; ngOnDestroy for subscription cleanup
- @Input() / input(): receive data from parent; @Output() / output(): emit events to parent via EventEmitter
- Signals (Angular 17+): signal(value), computed(fn), effect(fn) — fine-grained reactivity without Zone.js dirty checking
- Content projection: <ng-content select="[slot]"> — parent passes child elements into named slots
- ViewChild / ContentChild: get reference to a child component/element — access after ngAfterViewInit
- ChangeDetectionStrategy.OnPush: component only re-renders when @Input references change, event fires, or Observable/signal updates — dramatically improves performance
- Host element: the component selector tag itself — @HostBinding, @HostListener, or host: {} in @Component
Angular components: OnPush + Signals, computed, effect, content projection, ViewChild
// Standalone component with Signals (Angular 17+)
@Component({
selector: 'app-counter',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="counter">
<button (click)="decrement()">-</button>
<span>{{ count() }}</span>
<button (click)="increment()">+</button>
<p>Double: {{ doubled() }}</p>
</div>
`
})
export class CounterComponent {
// Signal-based inputs (Angular 17.1+)
initial = input(0);
// Writable signal
count = signal(0);
// Computed signal — automatically re-derives when count changes
doubled = computed(() => this.count() * 2);
constructor() {
// Effect — runs when signal changes, automatically tracked
effect(() => console.log('Count changed to', this.count()));
}
increment() { this.count.update(n => n + 1); }
decrement() { this.count.update(n => n - 1); }
}
// Content projection with named slots
@Component({
selector: 'app-card',
template: `
<div class="card">
<header><ng-content select="[card-title]"/></header>
<main><ng-content/></main>
<footer><ng-content select="[card-footer]"/></footer>
</div>
`
})
export class CardComponent {}
// Usage:
// <app-card>
// <h2 card-title>My Title</h2>
// <p>Card body content</p>
// <button card-footer>Save</button>
// </app-card>
// ViewChild — access child after view initialisation
@Component({ template: '<input #nameInput>' })
export class FormComponent implements AfterViewInit {
@ViewChild('nameInput') nameInput!: ElementRef<HTMLInputElement>;
ngAfterViewInit() { this.nameInput.nativeElement.focus(); }
}Real-World Example
ChangeDetectionStrategy.OnPush combined with Signals is the key to high-performance Angular apps. A default Angular app runs change detection on every browser event for every component in the tree. With OnPush + Signals, only components whose signals actually changed re-render — a 10x improvement in large component trees. The Angular team is moving toward Signal-based components as the default in future versions.