Skip to main content
Angular templates are HTML files enhanced with Angular-specific syntax that define component views. Templates use data binding, directives, and pipes to create dynamic user interfaces.

Template Syntax Overview

Angular templates support several types of binding syntax:

Interpolation

Display component data: {{ value }}

Property Binding

Set element properties: [property]="value"

Event Binding

Handle events: (event)="handler()"

Two-Way Binding

Combine property and event: [(ngModel)]="value"

Interpolation

Display component data in the template:
import { Component } from '@angular/core';

@Component({
  selector: 'app-interpolation',
  standalone: true,
  template: `
    <h1>{{ title }}</h1>
    <p>Welcome, {{ user.firstName }} {{ user.lastName }}!</p>
    <p>Total: {{ price * quantity }}</p>
    <p>{{ getMessage() }}</p>
  `
})
export class InterpolationComponent {
  title = 'My App';
  user = { firstName: 'John', lastName: 'Doe' };
  price = 10;
  quantity = 3;

  getMessage(): string {
    return 'Hello from method!';
  }
}
Interpolation expressions should be simple and side-effect free. Complex logic belongs in the component class.

Property Binding

Bind component data to element properties:
import { Component } from '@angular/core';

@Component({
  selector: 'app-property-binding',
  standalone: true,
  template: `
    <!-- Bind to native properties -->
    <img [src]="imageUrl" [alt]="imageAlt">
    <button [disabled]="isDisabled">Click Me</button>
    <input [value]="inputValue" [placeholder]="placeholder">
    
    <!-- Bind to attributes -->
    <div [attr.data-id]="userId">User Info</div>
    <button [attr.aria-label]="buttonLabel">Action</button>
    
    <!-- Bind to classes -->
    <div [class.active]="isActive">Status</div>
    <div [class]="cssClasses">Styled</div>
    
    <!-- Bind to styles -->
    <div [style.color]="textColor">Colored Text</div>
    <div [style.width.px]="width">Box</div>
    <div [style]="styleObject">Styled Box</div>
  `
})
export class PropertyBindingComponent {
  imageUrl = 'https://angular.dev/assets/logo.png';
  imageAlt = 'Angular logo';
  isDisabled = false;
  inputValue = 'Initial value';
  placeholder = 'Enter text...';
  userId = 123;
  buttonLabel = 'Perform action';
  isActive = true;
  cssClasses = 'card highlighted';
  textColor = 'blue';
  width = 200;
  styleObject = { color: 'red', fontSize: '16px' };
}

Event Binding

Respond to user interactions:
import { Component } from '@angular/core';

@Component({
  selector: 'app-event-binding',
  standalone: true,
  template: `
    <!-- Standard events -->
    <button (click)="onClick()">Click</button>
    <input (input)="onInput($event)" (blur)="onBlur()">
    <form (submit)="onSubmit($event)">
      <input type="text" [(ngModel)]="formValue">
      <button type="submit">Submit</button>
    </form>
    
    <!-- Event with $event object -->
    <input (keyup)="onKeyUp($event)">
    <div (mousemove)="onMouseMove($event)">Hover me</div>
    
    <!-- Template reference variable -->
    <input #inputRef (keyup)="onKeyUpRef(inputRef.value)">
    
    <p>Last key: {{ lastKey }}</p>
    <p>Form value: {{ formValue }}</p>
  `
})
export class EventBindingComponent {
  lastKey = '';
  formValue = '';

  onClick() {
    console.log('Button clicked!');
  }

  onInput(event: Event) {
    const target = event.target as HTMLInputElement;
    console.log('Input value:', target.value);
  }

  onBlur() {
    console.log('Input lost focus');
  }

  onSubmit(event: Event) {
    event.preventDefault();
    console.log('Form submitted:', this.formValue);
  }

  onKeyUp(event: KeyboardEvent) {
    this.lastKey = event.key;
  }

  onMouseMove(event: MouseEvent) {
    console.log('Mouse at:', event.clientX, event.clientY);
  }

  onKeyUpRef(value: string) {
    console.log('Value from ref:', value);
  }
}
Avoid calling methods with side effects in property bindings. Use event handlers for actions that modify state.

Two-Way Binding

Combine property and event binding:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-two-way-binding',
  standalone: true,
  imports: [FormsModule],
  template: `
    <input [(ngModel)]="name">
    <p>Hello, {{ name }}!</p>
    
    <input type="checkbox" [(ngModel)]="isChecked">
    <p>Checked: {{ isChecked }}</p>
    
    <select [(ngModel)]="selectedOption">
      <option value="option1">Option 1</option>
      <option value="option2">Option 2</option>
    </select>
    <p>Selected: {{ selectedOption }}</p>
  `
})
export class TwoWayBindingComponent {
  name = 'Angular';
  isChecked = false;
  selectedOption = 'option1';
}

Control Flow

Angular provides built-in control flow syntax for conditional rendering and loops.

@if - Conditional Rendering

import { Component } from '@angular/core';

@Component({
  selector: 'app-conditional',
  standalone: true,
  template: `
    <button (click)="toggle()">Toggle</button>
    
    @if (isVisible) {
      <div class="content">
        <p>Content is visible!</p>
      </div>
    }
    
    @if (user) {
      <p>Welcome, {{ user.name }}!</p>
    } @else {
      <p>Please log in.</p>
    }
    
    @if (status === 'loading') {
      <p>Loading...</p>
    } @else if (status === 'error') {
      <p class="error">Error occurred!</p>
    } @else {
      <p>Data loaded successfully.</p>
    }
  `
})
export class ConditionalComponent {
  isVisible = true;
  user: { name: string } | null = { name: 'John' };
  status: 'loading' | 'error' | 'success' = 'success';

  toggle() {
    this.isVisible = !this.isVisible;
  }
}

@for - Loops

import { Component } from '@angular/core';

@Component({
  selector: 'app-loops',
  standalone: true,
  template: `
    <ul>
      @for (item of items; track item.id) {
        <li>{{ item.name }} - {{ item.price | currency }}</li>
      }
    </ul>
    
    @for (user of users; track user.id; let i = $index, first = $first) {
      <div [class.first]="first">
        {{ i + 1 }}. {{ user.name }}
      </div>
    } @empty {
      <p>No users found.</p>
    }
    
    <!-- Accessing loop variables -->
    @for (item of collection; track item.id; let idx = $index, count = $count) {
      <div>Item {{ idx + 1 }} of {{ count }}: {{ item.name }}</div>
    }
  `
})
export class LoopsComponent {
  items = [
    { id: 1, name: 'Item 1', price: 10 },
    { id: 2, name: 'Item 2', price: 20 },
    { id: 3, name: 'Item 3', price: 30 }
  ];

  users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' }
  ];

  collection = [
    { id: 1, name: 'First' },
    { id: 2, name: 'Second' },
    { id: 3, name: 'Third' }
  ];
}
Always provide a track expression in @for loops to help Angular identify items and optimize rendering.

@switch - Switch Statements

import { Component } from '@angular/core';

@Component({
  selector: 'app-switch',
  standalone: true,
  template: `
    <select [(ngModel)]="selectedColor">
      <option value="red">Red</option>
      <option value="green">Green</option>
      <option value="blue">Blue</option>
    </select>
    
    @switch (selectedColor) {
      @case ('red') {
        <div class="red">You selected red!</div>
      }
      @case ('green') {
        <div class="green">You selected green!</div>
      }
      @case ('blue') {
        <div class="blue">You selected blue!</div>
      }
      @default {
        <div>Select a color</div>
      }
    }
  `
})
export class SwitchComponent {
  selectedColor = 'red';
}

Template Reference Variables

Create local variables to reference elements:
import { Component } from '@angular/core';

@Component({
  selector: 'app-template-refs',
  standalone: true,
  template: `
    <input #nameInput type="text">
    <button (click)="greet(nameInput.value)">Greet</button>
    
    <video #videoPlayer src="video.mp4"></video>
    <button (click)="videoPlayer.play()">Play</button>
    <button (click)="videoPlayer.pause()">Pause</button>
    
    <form #myForm="ngForm">
      <input name="email" ngModel required>
      <p>Form valid: {{ myForm.valid }}</p>
    </form>
  `
})
export class TemplateRefsComponent {
  greet(name: string) {
    alert(`Hello, ${name}!`);
  }
}

Safe Navigation Operator

Handle null and undefined values safely:
import { Component } from '@angular/core';

@Component({
  selector: 'app-safe-navigation',
  standalone: true,
  template: `
    <!-- Without safe navigation - may cause errors -->
    <p>{{ user.profile.email }}</p>
    
    <!-- With safe navigation - safe -->
    <p>{{ user?.profile?.email }}</p>
    
    <!-- Optional with default value -->
    <p>{{ user?.profile?.email ?? 'No email' }}</p>
  `
})
export class SafeNavigationComponent {
  user: any = null; // May be null or undefined
}

Best Practices

  1. Keep templates simple - Move complex logic to the component
  2. Use track in @for - Improves performance
  3. Avoid method calls in templates - Cache computed values
  4. Use safe navigation - Prevent null reference errors
  5. Prefer @if over ngIf - Modern control flow syntax
  6. Use OnPush change detection - With immutable data patterns

Next Steps

Directives

Learn about built-in and custom directives

Pipes

Transform data in templates

Build docs developers (and LLMs) love