Tutorials Logic, IN info@tutorialslogic.com

Angular Template Syntax Binding Control Flow

Interpolation

Angular templates bind component state to DOM properties, text, events, forms, and control-flow blocks. Learn each binding direction separately, then combine them without hiding application logic inside template expressions.

Use double curly braces {{ }} to embed expressions in the template. Angular evaluates the expression and converts the result to a string.

Expressions can read component members, signals, literals, operators, optional chaining, and pipes, but they are not full JavaScript. Keep them deterministic and inexpensive; move allocation, sorting, and business rules into computed state or a pure pipe.

  • Call a signal getter, such as userName(), so Angular tracks it as a template dependency.
  • Use optional chaining and nullish coalescing for values that are legitimately absent.
  • Interpolation escapes text; it does not interpret the value as trusted HTML.

Interpolation Example

Interpolation Example
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']);
}

Interpolation Setup

Interpolation Setup
<!-- 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".

The target can be a native property, a component or directive input, an attribute through attr., a class, or a style. Bind disabled, value, and checked as properties; use attr.aria-label and other attribute bindings when no corresponding writable property exists.

  • A null attribute binding removes the attribute.
  • Boolean DOM properties must receive booleans rather than string values such as "false".
  • Use class.active and style.width.px for one value, or stable class and style maps for several values.

Property Binding Example

Property Binding Example
<!-- 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)".

The event expression may assign local state or call a short component method. Use $event when the handler needs the typed event object, and use key modifiers such as keydown.enter for keyboard-specific behavior.

  • Prefer semantic button and link elements before adding keyboard handlers to generic elements.
  • Call preventDefault or stopPropagation only when the interaction contract truly requires it.
  • A component output uses the same syntax, but $event is the payload type declared by that component.

Event Binding Example

Event Binding Example
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');
    }
}

Event Binding Setup

Event Binding Setup
<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

Two-way syntax combines an input named value with an output named valueChange. Use FormsModule and [(ngModel)] for form controls, or expose a model() field from a child component for a typed custom two-way contract.

Use separate property and event bindings when the parent must validate, transform, reject, or log the proposed change before updating state. Two-way binding should express genuine shared ownership, not hide a command.

  • Every ngModel control inside a form needs a name unless it is marked standalone.
  • Read and update a model signal as a signal inside the child.
  • Do not combine @Input with a WritableSignal expecting Angular to replace the signal value automatically.

Two-way Binding Example

Two-way Binding Example
import { Component } 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 = '';
}

Two-way Binding Setup

Two-way Binding Setup
<!-- [(ngModel)] requires FormsModule -->
<input [(ngModel)]="username" placeholder="Enter name" />
<p>Hello, {{ username }}!</p>

<!-- Equivalent manual two-way -->
<input [value]="username" (input)="username = $any($event.target).value" />

Built-in Control Flow

Use @if, @for, and @switch for conditional and repeated template regions. @for requires a track expression and supports @empty; choose a stable domain key so Angular can retain DOM and component state as the collection changes.

Use @let to name one expression for the current view and @defer to split noncritical dependencies with explicit placeholder, loading, and error states. A template variable follows view scope and cannot be reassigned like a component field.

@if, @for, @switch

@if, @for, @switch
<!-- @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>
}

Built-in Control Flow - HTML Example

Built-in Control Flow - HTML Example
<!-- @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>
}

Built-in Control Flow Usage

Built-in Control Flow Usage
<!-- @switch -->
@switch (status()) {
    @case ('loading') {
        <app-spinner />
    }
    @case ('error') {
        <app-error-message />
    }
    @case ('success') {
        <app-data-table />
    }
    @default {
        <p>Unknown status</p>
    }
}

Template Variables

A template reference such as #search points to the element, component, directive, TemplateRef, or exported API at that location. Its scope is the current view; control-flow blocks create child views with their own variable boundaries.

@let stores the current value of an expression for reuse within its view. It updates when the expression changes but cannot be assigned from an event handler.

  • Use #form="ngForm" or another exportAs name to access a directive API.
  • Pass a reference to a handler when an imperative DOM action is unavoidable, but prefer bindings and signal queries in component code.
  • Do not expect a variable declared inside @if or @for to exist after that block.

Template Variables Example

Template Variables Example
<!-- #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 />
}

Template Design

A template should make data flow visible. Use semantic HTML first, bind the smallest necessary values, label interactive controls, and keep event expressions short enough that their intent is obvious.

Use @if for conditional views, @for with a stable track expression for collections, @switch for exclusive states, @let for one reusable expression, and @defer only for noncritical dependencies. Give placeholders stable dimensions when deferral could move surrounding content.

Template reference variables point to an element, directive, component, or exported form directive in the current view. They are not general mutable variables and do not cross view boundaries created by control flow.

  • Keep templates declarative: bind already prepared state, handle user events, and let control-flow blocks express which views exist. Move data fetching, validation policy, and expensive transformations into TypeScript services, state, computed values, or pure pipes.
Confirm the page outcome

Template Review

4 checks
  • I can distinguish text interpolation, properties, attributes, classes, styles, events, inputs, and outputs.
  • I keep template expressions deterministic and move expensive transformation or business rules into prepared state.
  • I can choose @if, @for, or @switch and provide a stable track expression for repeated items.
  • I can use @let, @defer, reference variables, ngModel, and model inputs without crossing their ownership or scope boundaries.

Template Review Questions

Do not assume a signal behaves like a plain assignable property. A writable signal is read with username() and changed with set or update, while ngModel’s traditional two-way binding expects to assign to its target.

The track expression tells Angular which logical item each rendered view represents. Tracking by a non-unique or changing value can cause Angular to reuse the wrong DOM node when the array is reordered, inserted into, or filtered.

Square-bracket property binding normally writes to a DOM element property, which is appropriate for src, disabled, and value. Some values, including many ARIA attributes, are attributes rather than corresponding writable DOM properties in Angular templates.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.