Components & Templates

Templates and Data Binding

Master Angular's template syntax — interpolation, property binding, event binding, two-way binding, and template reference variables.

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

BindingSyntaxDirectionExample
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:

html
<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

html
<!-- 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

html
<!-- ❌ 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

html
<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.:

html
<table>
  <td [attr.colspan]="columnSpan">Wide Cell</td>
</table>
<button [attr.aria-label]="buttonLabel">✕</button>
<div [attr.role]="'navigation'">Nav</div>

Class Binding

html
<!-- Single class toggle -->
<div [class.active]="isActive">Tab</div>
<div [class.error]="hasError">Message</div>

<!-- Multiple classes -->
<div [class]="currentClasses">Content</div>
typescript
export class AppComponent {
  isActive = true;
  hasError = false;
  currentClasses = 'card shadow rounded';
}

Style Binding

html
<!-- 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

html
<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:

typescript
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:

html
<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):

typescript
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:

html
<input [ngModel]="name" (ngModelChange)="name = $event">

Custom Two-Way Binding

You can create two-way binding on your own components:

typescript
@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);
  }
}
html
<!-- 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:

html
<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+)

html
@if (user) {
  <app-user-card [user]="user" />
} @else if (isLoading) {
  <app-spinner />
} @else {
  <p>No user found.</p>
}

@for with Track (Angular 17+)

html
<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

typescript
@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.