Template Syntax in Angular
Interpolation
Use double curly braces {{ }} to embed expressions in the template. Angular evaluates the expression and converts the result to a string.
import { Component, signal } from '@angular/core';
@Component({ selector: 'app-demo', standalone: true, templateUrl: './demo.component.html' })
export class DemoComponent {
name = signal('Angular');
version = signal(21);
price = signal(9.99);
items = signal(['Apple', 'Banana', 'Cherry']);
}
<!-- Basic interpolation -->
<h1>Hello, {{ name() }}!</h1>
<p>Version: {{ version() }}</p>
<!-- Expressions -->
<p>Price: ${{ price() | number:'1.2-2' }}</p>
<p>Items: {{ items().length }}</p>
<p>Upper: {{ name().toUpperCase() }}</p>
<p>Sum: {{ 1 + 2 + 3 }}</p>
Property Binding
Bind a DOM property to a component expression using square brackets [property]="expression".
<!-- Bind to DOM properties -->
<img [src]="imageUrl()" [alt]="imageAlt()" />
<button [disabled]="isLoading()">Submit</button>
<input [value]="username()" />
<!-- Class and style binding -->
<div [class.active]="isActive()">...</div>
<div [class]="{ active: isActive(), error: hasError() }">...</div>
<p [style.color]="textColor()">Styled text</p>
<p [style]="{ fontSize: fontSize() + 'px', fontWeight: 'bold' }">...</p>
<!-- Attribute binding (for non-DOM attributes like aria-*) -->
<button [attr.aria-label]="buttonLabel()">Click</button>
Event Binding
Listen to DOM events using parentheses (event)="handler($event)".
export class EventDemoComponent {
count = signal(0);
inputValue = signal('');
increment() { this.count.update(v => v + 1); }
onInput(event: Event) {
this.inputValue.set((event.target as HTMLInputElement).value);
}
onKeydown(event: KeyboardEvent) {
if (event.key === 'Enter') console.log('Enter pressed');
}
}
<button (click)="increment()">Count: {{ count() }}</button>
<input (input)="onInput($event)" (keydown)="onKeydown($event)" />
<p>You typed: {{ inputValue() }}</p>
<!-- Inline expression -->
<button (click)="count.set(0)">Reset</button>
Two-Way Binding
import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-two-way',
standalone: true,
imports: [FormsModule],
templateUrl: './two-way.component.html'
})
export class TwoWayComponent {
username = signal('');
}
<!-- [(ngModel)] requires FormsModule -->
<input [(ngModel)]="username" placeholder="Enter name" />
<p>Hello, {{ username() }}!</p>
<!-- Equivalent manual two-way -->
<input [value]="username()" (input)="username.set($any($event.target).value)" />
Built-in Control Flow (Angular 17+)
Angular 17 introduced a new built-in control flow syntax using @if, @for, and @switch — replacing *ngIf, *ngFor, and *ngSwitch. This is the recommended approach in Angular 21.
<!-- @if / @else if / @else -->
@if (isLoggedIn()) {
<p>Welcome back, {{ username() }}!</p>
} @else if (isGuest()) {
<p>Browsing as guest</p>
} @else {
<a href="/login">Please log in</a>
}
<!-- @for with track (required) -->
@for (item of items(); track item.id) {
<li>{{ item.name }} — ${{ item.price }}</li>
} @empty {
<li>No items found</li>
}
<!-- Loop variables -->
@for (item of items(); track item.id; let i = $index, last = $last) {
<li [class.last]="last">{{ i + 1 }}. {{ item.name }}</li>
}
<!-- @switch -->
@switch (status()) {
@case ('loading') {
<app-spinner />
}
@case ('error') {
<app-error-message />
}
@case ('success') {
<app-data-table />
}
@default {
<p>Unknown status</p>
}
}
Template Reference Variables
<!-- #ref creates a reference to the element -->
<input #nameInput type="text" />
<button (click)="greet(nameInput.value)">Greet</button>
<!-- Reference to a component -->
<app-child #child />
<button (click)="child.doSomething()">Call child method</button>
<!-- @defer — lazy load a block -->
@defer (on viewport) {
<app-heavy-component />
} @placeholder {
<p>Scroll down to load...</p>
} @loading {
<app-spinner />
}