Angular's Template System
Angular templates are HTML enhanced with Angular-specific syntax. They describe how the component should render its view and how the view interacts with the component class.
The Four Binding Types
| Binding | Syntax | Direction | Example |
|---|---|---|---|
| Interpolation | {{ }} | Component → View | {{ title }} |
| Property | [prop] | Component → View | [src]="imageUrl" |
| Event | (event) | View → Component | (click)="save()" |
| Two-Way | [( )] | Both directions | [(ngModel)]="name" |
Interpolation — {{ expression }}
Renders a component property or expression as text:
<h1>{{ title }}</h1>
<p>Welcome, {{ user.firstName }} {{ user.lastName }}!</p>
<p>Total: {{ price * quantity | currency }}</p>
<p>Items: {{ items.length }}</p>
<p>Status: {{ isActive ? 'Active' : 'Inactive' }}</p>What You Can Do in Interpolation
<!-- Method calls -->
<p>{{ getFullName() }}</p>
<!-- Ternary expressions -->
<p>{{ count > 0 ? count + ' items' : 'Empty' }}</p>
<!-- Pipe transforms -->
<p>{{ today | date:'fullDate' }}</p>What You Cannot Do
<!-- ❌ Assignments -->
<p>{{ count = 5 }}</p>
<!-- ❌ new keyword -->
<p>{{ new Date() }}</p>
<!-- ❌ Chained expressions with ; -->
<p>{{ doThis(); doThat() }}</p>Property Binding — [property]="expression"
Binds a DOM element property (or directive/component input) to a component expression:
DOM Property Binding
<img [src]="product.imageUrl" [alt]="product.name">
<button [disabled]="!isFormValid">Submit</button>
<a [href]="profileLink">View Profile</a>
<input [value]="defaultValue" [placeholder]="placeholder">
<div [hidden]="isSecret">Secret content</div>Attribute Binding
Some HTML attributes don't have corresponding DOM properties. Use attr.:
<table>
<td [attr.colspan]="columnSpan">Wide Cell</td>
</table>
<button [attr.aria-label]="buttonLabel">✕</button>
<div [attr.role]="'navigation'">Nav</div>Class Binding
<!-- Single class toggle -->
<div [class.active]="isActive">Tab</div>
<div [class.error]="hasError">Message</div>
<!-- Multiple classes -->
<div [class]="currentClasses">Content</div>export class AppComponent {
isActive = true;
hasError = false;
currentClasses = 'card shadow rounded';
}Style Binding
<!-- Single style -->
<div [style.color]="textColor">Colored text</div>
<div [style.font-size.px]="fontSize">Sized text</div>
<div [style.width.%]="widthPercent">Bar</div>
<!-- Multiple styles -->
<div [style]="currentStyles">Styled content</div>Event Binding — (event)="handler()"
Responds to user interactions and DOM events:
Common Events
<button (click)="onSave()">Save</button>
<input (input)="onSearch($event)">
<input (keyup.enter)="onSubmit()">
<form (submit)="onFormSubmit($event)">
<div (mouseenter)="onHover(true)" (mouseleave)="onHover(false)">
Hover Zone
</div>
<select (change)="onSelect($event)">
<option>Option 1</option>
<option>Option 2</option>
</select>The $event Object
$event provides details about the event that occurred:
export class SearchComponent {
searchTerm = '';
onSearch(event: Event) {
const input = event.target as HTMLInputElement;
this.searchTerm = input.value;
}
onKeyDown(event: KeyboardEvent) {
if (event.key === 'Escape') {
this.searchTerm = '';
}
}
}Key Event Filtering
Angular supports filtering keyboard events by key name:
<input (keyup.enter)="submit()">
<input (keydown.escape)="cancel()">
<input (keyup.shift.alt.t)="openTerminal()">Two-Way Binding — [(ngModel)]
Combines property binding (component → view) and event binding (view → component):
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-profile-editor',
standalone: true,
imports: [FormsModule],
template: `
<div>
<label>Name:</label>
<input [(ngModel)]="name" placeholder="Enter name">
<label>Email:</label>
<input [(ngModel)]="email" type="email">
<h3>Preview</h3>
<p>Name: {{ name }}</p>
<p>Email: {{ email }}</p>
</div>
`,
})
export class ProfileEditorComponent {
name = '';
email = '';
}How Two-Way Binding Works
[(ngModel)]="name" is syntactic sugar for:
<input [ngModel]="name" (ngModelChange)="name = $event">Custom Two-Way Binding
You can create two-way binding on your own components:
@Component({
selector: 'app-counter',
standalone: true,
template: `
<button (click)="decrement()">-</button>
<span>{{ count }}</span>
<button (click)="increment()">+</button>
`,
})
export class CounterComponent {
@Input() count = 0;
@Output() countChange = new EventEmitter<number>();
increment() {
this.count++;
this.countChange.emit(this.count);
}
decrement() {
this.count--;
this.countChange.emit(this.count);
}
}<!-- Parent uses two-way binding syntax -->
<app-counter [(count)]="totalItems" />
<p>Total: {{ totalItems }}</p>Pattern: For [(x)] to work, the component needs @Input() x and @Output() xChange.
Template Reference Variables — #ref
Create a reference to a DOM element or component:
<input #emailInput type="email" placeholder="Enter email">
<button (click)="sendInvite(emailInput.value)">
Send Invite
</button>
<!-- Reference a component -->
<app-timer #timer />
<button (click)="timer.start()">Start Timer</button>
<button (click)="timer.stop()">Stop Timer</button>Conditional Display
@if (Angular 17+)
@if (user) {
<app-user-card [user]="user" />
} @else if (isLoading) {
<app-spinner />
} @else {
<p>No user found.</p>
}@for with Track (Angular 17+)
<ul>
@for (product of products; track product.id; let i = $index) {
<li>{{ i + 1 }}. {{ product.name }} — {{ product.price | currency }}</li>
} @empty {
<li>No products available.</li>
}
</ul>Practical Example
@Component({
selector: 'app-product-list',
standalone: true,
imports: [FormsModule],
template: `
<input [(ngModel)]="filterText" placeholder="Filter products...">
<p>Showing {{ filteredProducts.length }} of {{ products.length }}</p>
<div class="grid">
@for (product of filteredProducts; track product.id) {
<div class="card"
[class.featured]="product.featured"
(click)="selectProduct(product)">
<img [src]="product.image" [alt]="product.name">
<h3>{{ product.name }}</h3>
<p [style.color]="product.onSale ? 'red' : 'inherit'">
{{ product.price | currency }}
</p>
</div>
}
</div>
`,
})
export class ProductListComponent {
filterText = '';
products: Product[] = [/* ... */];
get filteredProducts(): Product[] {
return this.products.filter(p =>
p.name.toLowerCase().includes(this.filterText.toLowerCase())
);
}
selectProduct(product: Product) {
console.log('Selected:', product.name);
}
}Next Steps
Now that you understand data binding and templates, let's explore directives — reusable behaviors that you can attach to DOM elements to extend their functionality.