Template Syntax
Interpolation
<!-- String interpolation -->
<p>{{ title }}</p>
<p>{{ user.name }}</p>
<p>{{ 1 + 1 }}</p>
<p>{{ getName() }}</p>
<p>{{ user?.name }}</p> <!-- Safe navigation -->
Property Binding
<!-- Element property -->
<img [src]="imageUrl">
<button [disabled]="isDisabled">Click</button>
<input [value]="username">
<!-- Class binding -->
<div [class.active]="isActive">Text</div>
<div [class]="'btn btn-primary'">Button</div>
<div [class]="classes">Dynamic classes</div>
<!-- Style binding -->
<div [style.color]="textColor">Colored text</div>
<div [style.font-size.px]="fontSize">Text</div>
<div [style]="{'color': 'red', 'font-size': '14px'}">Styled</div>
<!-- Attribute binding -->
<button [attr.aria-label]="label">Button</button>
<div [attr.data-id]="userId">Data</div>
Event Binding
<!-- Basic events -->
<button (click)="onClick()">Click</button>
<input (input)="onInput($event)">
<input (keyup)="onKeyup($event)">
<input (keyup.enter)="onEnter()">
<!-- Event with parameter -->
<button (click)="onDelete(user.id)">Delete</button>
<!-- $event object -->
<input (input)="onInput($event)">
<!-- Multiple events -->
<input
(focus)="onFocus()"
(blur)="onBlur()"
(input)="onInput($event)">
onInput(event: Event) {
const value = (event.target as HTMLInputElement).value;
console.log(value);
}
Two-way Binding
<!-- Using ngModel (FormsModule) -->
<input [(ngModel)]="username">
<!-- Equivalent to: -->
<input
[ngModel]="username"
(ngModelChange)="username = $event">
<!-- Custom two-way binding -->
<app-counter [(count)]="counter"></app-counter>
// Custom two-way binding in component (signal-based)
import { Component, input, output } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true
})
export class CounterComponent {
count = input<number>(0);
countChange = output<number>();
increment() {
const newCount = this.count() + 1;
this.countChange.emit(newCount);
}
}
Template Reference Variables
<!-- Element reference -->
<input #nameInput>
<button (click)="nameInput.focus()">Focus</button>
<!-- Component reference -->
<app-child #child></app-child>
<button (click)="child.doSomething()">Do</button>
<!-- Form reference -->
<form #userForm="ngForm">
<input name="username" ngModel>
<button [disabled]="userForm.invalid">Submit</button>
</form>
<!-- Directive reference -->
<div appHighlight #highlight="appHighlight">
<button (click)="highlight.changeColor('red')">Red</button>
</div>
Structural Directives
<!-- *ngIf -->
<div *ngIf="isVisible">Visible</div>
<div *ngIf="user; else loading">{{ user.name }}</div>
<ng-template #loading>Loading...</ng-template>
<!-- *ngIf with as -->
<div *ngIf="user$ | async as user">
{{ user.name }}
</div>
<!-- *ngFor -->
<li *ngFor="let item of items">{{ item }}</li>
<li *ngFor="let item of items; index as i">
{{ i }}: {{ item }}
</li>
<li *ngFor="let item of items; trackBy: trackByFn">
{{ item.name }}
</li>
<!-- *ngFor variables -->
<div *ngFor="let item of items;
index as i;
first as isFirst;
last as isLast;
even as isEven;
odd as isOdd">
{{ i }}: {{ item }}
</div>
<!-- *ngSwitch -->
<div [ngSwitch]="status">
<p *ngSwitchCase="'active'">Active</p>
<p *ngSwitchCase="'inactive'">Inactive</p>
<p *ngSwitchDefault>Unknown</p>
</div>
ng-template
<!-- Basic template -->
<ng-template #template>
<p>Template content</p>
</ng-template>
<!-- Conditional template -->
<div *ngIf="show; else elseBlock">Main content</div>
<ng-template #elseBlock>
<p>Else content</p>
</ng-template>
<!-- Template with context -->
<ng-template #userTemplate let-user let-i="index">
<p>{{ i }}: {{ user.name }}</p>
</ng-template>
<!-- Use with ngTemplateOutlet -->
<ng-container *ngTemplateOutlet="userTemplate; context: { $implicit: user, index: 0 }">
</ng-container>
ng-container
<!-- Grouping without extra DOM element -->
<ng-container *ngIf="isVisible">
<h1>Title</h1>
<p>Content</p>
</ng-container>
<!-- Multiple structural directives -->
<ng-container *ngFor="let item of items">
<div *ngIf="item.active">{{ item.name }}</div>
</ng-container>
ng-content
<!-- Component with content projection -->
@Component({
selector: 'app-card',
standalone: true,
template: `
<div class="card">
<ng-content></ng-content>
</div>
`
})
<!-- Usage -->
<app-card>
<h2>Card Title</h2>
<p>Card content</p>
</app-card>
<!-- Multiple slots -->
@Component({
selector: 'app-layout',
standalone: true,
template: `
<header>
<ng-content select="[slot=header]"></ng-content>
</header>
<main>
<ng-content></ng-content>
</main>
<footer>
<ng-content select="[slot=footer]"></ng-content>
</footer>
`
})
<!-- Usage -->
<app-layout>
<div slot="header">Header content</div>
<p>Main content</p>
<div slot="footer">Footer content</div>
</app-layout>
Pipes in Templates
<!-- Basic pipe -->
<p>{{ name | uppercase }}</p>
<!-- Pipe with parameters -->
<p>{{ price | currency:'USD':'symbol':'1.2-2' }}</p>
<!-- Chained pipes -->
<p>{{ date | date:'short' | uppercase }}</p>
<!-- Async pipe -->
<p>{{ user$ | async }}</p>
<div *ngIf="users$ | async as users">
<p *ngFor="let user of users">{{ user.name }}</p>
</div>
Safe Navigation Operator
<!-- Safe navigation (?) -->
<p>{{ user?.name }}</p>
<p>{{ user?.address?.city }}</p>
<!-- Without safe navigation (throws error if null) -->
<p>{{ user.name }}</p> <!-- Error if user is null -->
<!-- Combination with async -->
<p>{{ (user$ | async)?.name }}</p>
Non-null Assertion
<!-- Non-null assertion (!) -->
<p>{{ user!.name }}</p> <!-- Tells TS user is not null -->
Template Expressions
<!-- Arithmetic -->
<p>{{ 1 + 1 }}</p>
<p>{{ price * quantity }}</p>
<!-- Comparison -->
<p>{{ count > 10 }}</p>
<p>{{ status === 'active' }}</p>
<!-- Logical -->
<p>{{ isActive && isVisible }}</p>
<p>{{ user || 'Guest' }}</p>
<!-- Ternary -->
<p>{{ isActive ? 'Active' : 'Inactive' }}</p>
<!-- Method calls -->
<p>{{ getFullName() }}</p>
<p>{{ user.getName() }}</p>
<!-- Limitations: -->
<!-- ❌ No assignments: count = 5 -->
<!-- ❌ No new/typeof/instanceof -->
<!-- ❌ No chaining: ; -->
<!-- ❌ No increment/decrement: count++ -->
Best Practices
- Keep template expressions simple
- Use pipes for transformations
- Use trackBy with *ngFor
- Use async pipe for observables
- Use safe navigation for nullable values
- Avoid complex logic in templates (move to component)
- Use ng-container to avoid extra DOM elements
- Use ng-template for reusable templates