Angular Components & Templates
Master Angular components — the @Component decorator, all four binding types, component inputs/outputs, lifecycle hooks, and content projection.
Every Angular app is a tree of components. A component controls a piece of the UI — it has a TypeScript class (logic), an HTML template (view), and optional CSS styles. This lesson covers everything about components from creation to communication.
Anatomy of a Component
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-user-card', // how you use it in HTML
standalone: true,
imports: [CommonModule], // other components/directives used in template
templateUrl: './user-card.component.html',
styleUrls: ['./user-card.component.css'],
})
export class UserCardComponent implements OnInit {
name = 'Alice';
age = 30;
ngOnInit(): void {
// runs once after inputs are set
console.log('Component initialized');
}
}Use it in a parent template:
<app-user-card />The Four Binding Types
1. Interpolation — {{ expression }}
Render data in the template:
<h2>{{ name }}</h2>
<p>Age: {{ age }}</p>
<p>{{ 2 + 2 }}</p>
<p>{{ name.toUpperCase() }}</p>2. Property Binding — [property]="expression"
Bind a DOM property or component input to a class field:
<img [src]="avatarUrl" [alt]="name" />
<button [disabled]="isLoading">Submit</button>
<input [value]="searchTerm" />3. Event Binding — (event)="handler($event)"
Listen to DOM events and call class methods:
<button (click)="save()">Save</button>
<input (input)="onInput($event)" />
<form (submit)="onSubmit($event)">...</form>In the class:
save() {
console.log('Saved!');
}
onInput(event: Event) {
const value = (event.target as HTMLInputElement).value;
console.log(value);
}4. Two-Way Binding — [(ngModel)]
Sync a property with a form input (requires FormsModule):
@Component({
standalone: true,
imports: [FormsModule],
template: `
<input [(ngModel)]="name" />
<p>Hello, {{ name }}</p>
`,
})
export class ExampleComponent {
name = 'Alice';
}[(ngModel)] is shorthand for [ngModel]="name" (ngModelChange)="name = $event".
Component Inputs — @Input()
Pass data from a parent component down to a child:
Child component:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
template: `
<div class="card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
`,
})
export class UserCardComponent {
@Input() user!: { name: string; email: string };
@Input() showEmail = true; // optional input with default
}Parent template:
<app-user-card [user]="currentUser" [showEmail]="false" />Required Inputs (Angular 16+)
@Input({ required: true }) user!: User;Angular will throw a build error if the parent doesn't provide this input.
Component Outputs — @Output() + EventEmitter
Send events from child to parent:
Child component:
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-confirm-dialog',
standalone: true,
template: `
<div class="dialog">
<p>Are you sure?</p>
<button (click)="confirm()">Yes</button>
<button (click)="cancel()">No</button>
</div>
`,
})
export class ConfirmDialogComponent {
@Output() confirmed = new EventEmitter<void>();
@Output() cancelled = new EventEmitter<void>();
confirm() { this.confirmed.emit(); }
cancel() { this.cancelled.emit(); }
}Parent template:
<app-confirm-dialog
(confirmed)="onConfirm()"
(cancelled)="onCancel()"
/>Lifecycle Hooks
Angular calls these methods at defined points in a component's life:
| Hook | When it runs |
|------|-------------|
| ngOnInit | Once, after first ngOnChanges. Use for data fetching. |
| ngOnChanges | Every time an @Input() value changes. |
| ngOnDestroy | Just before the component is destroyed. Use to unsubscribe. |
| ngAfterViewInit | After the component's view is fully initialized. |
| ngDoCheck | On every change detection run. Use carefully — runs often. |
import { Component, OnInit, OnDestroy, OnChanges, SimpleChanges, Input } from '@angular/core';
import { Subscription } from 'rxjs';
@Component({ selector: 'app-example', standalone: true, template: '' })
export class ExampleComponent implements OnInit, OnChanges, OnDestroy {
@Input() userId!: number;
private sub!: Subscription;
ngOnChanges(changes: SimpleChanges): void {
if (changes['userId']) {
console.log('userId changed:', changes['userId'].currentValue);
}
}
ngOnInit(): void {
// safe to use this.userId here
this.loadUser(this.userId);
}
ngOnDestroy(): void {
// clean up subscriptions to prevent memory leaks
this.sub?.unsubscribe();
}
private loadUser(id: number) { /* ... */ }
}Template Variables — #ref
Reference a DOM element or child component directly in the template:
<input #searchInput type="text" />
<button (click)="search(searchInput.value)">Search</button><!-- Access a child component's public methods -->
<app-counter #counter />
<button (click)="counter.reset()">Reset Counter</button>Content Projection — <ng-content>
Let parent components inject HTML into a child component's template (like React's children):
Card component:
@Component({
selector: 'app-card',
standalone: true,
template: `
<div class="card">
<div class="card-header">
<ng-content select="[card-title]" />
</div>
<div class="card-body">
<ng-content />
</div>
</div>
`,
})
export class CardComponent {}Parent:
<app-card>
<h2 card-title>User Profile</h2>
<p>This goes into the card body.</p>
</app-card>ViewChild — Access Child Components in Code
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { ChartComponent } from './chart.component';
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [ChartComponent],
template: `<app-chart #chart />`,
})
export class DashboardComponent implements AfterViewInit {
@ViewChild('chart') chart!: ChartComponent;
ngAfterViewInit(): void {
this.chart.refresh(); // call child method after view initializes
}
}Inline Templates and Styles
For small components, keep everything in one file:
@Component({
selector: 'app-badge',
standalone: true,
template: `<span class="badge">{{ label }}</span>`,
styles: [`
.badge {
background: #7c3aed;
color: white;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
`],
})
export class BadgeComponent {
@Input() label = '';
}Quick Reference
Create component: ng g c component-name
Interpolation: {{ value }}
Property binding: [property]="value"
Event binding: (event)="handler()"
Two-way binding: [(ngModel)]="value" (needs FormsModule)
Pass data down: @Input() prop!: Type
Emit events up: @Output() event = new EventEmitter()
Template ref: #refName → use in template or @ViewChild
Content slot: in child, projected from parent
Key lifecycle: ngOnInit (fetch), ngOnDestroy (unsubscribe) Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.