Skip to content

Angular Templates — Complete Guide to Template Syntax and Data Binding

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Angular Templates. We cover key concepts, practical examples, and best practices to help you master this topic.

Angular templates use extended HTML syntax to bind data from the component class to the DOM, handle user events, and control rendering flow with directives.

What You'll Learn

  • How interpolation and property binding differ
  • How to handle user events with event binding
  • How two-way binding keeps form inputs in sync
  • How template reference variables access DOM elements
  • How to use safe navigation and non-null assertion

Why It Matters

Templates are where your app communicates with users. Without template bindings, your components would be static HTML. Mastering template syntax lets you build interactive, data-driven UIs efficiently.

Real-World Use

Think of a search bar in Doda Browser's settings panel. The input field uses two-way binding to keep the search query in sync, an event binding on keystroke triggers filtering, and the results display uses property binding to show or hide content.

flowchart LR
    A[Component Data] -->|Interpolation| B[Display Text]
    A -->|Property Binding| C[Element Properties]
    D[User Events] -->|Event Binding| A
    E[Form Inputs] <-->|Two-Way Binding| A
    style A fill:#f97316,color:#fff

Interpolation

Interpolation uses double curly braces {{ }} to insert component property values into the template:

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

@Component({
  selector: "app-user-profile",
  standalone: true,
  template: `
    <h1>Welcome, {{ username }}!</h1>
    <p>You have {{ notificationCount }} new notifications.</p>
    <p>Last login: {{ lastLogin | date }}</p>
  `
})
export class UserProfileComponent {
  username = "alice_dev";
  notificationCount = 5;
  lastLogin = new Date();
}

Expected output: "Welcome, alice_dev!" followed by notification count and the formatted login date.

Inside the curly braces, you can write any JavaScript expression that Angular can evaluate. This includes arithmetic, ternary operators, and method calls, but not statements like if or for.

Property Binding

Property binding sets an element's property to a component value using brackets []:

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

@Component({
  selector: "app-image-viewer",
  standalone: true,
  template: `
    <img [src]="imageUrl" [alt]="imageAlt" [width]="imageWidth" />
    <button [disabled]="isLoading">{{ isLoading ? "Loading..." : "Load Next" }}</button>
  `
})
export class ImageViewerComponent {
  imageUrl = "https://example.com/photo.jpg";
  imageAlt = "A scenic mountain view";
  imageWidth = 600;
  isLoading = false;
}

Expected output: An image with the specified source, alt text, and width. The button is enabled because isLoading is false.

Property binding is one-directional: the component sends data to the template. When the component property changes, Angular updates the DOM property. Use property binding instead of string interpolation for anything that is not text content.

Event Binding

Event binding listens to DOM events using parentheses ():

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

@Component({
  selector: "app-click-counter",
  standalone: true,
  template: `
    <p>You clicked {{ clickCount }} times</p>
    <button (click)="increment()">Click me</button>
    <button (click)="reset()">Reset</button>
    <input (keyup.enter)="onEnter($event)" placeholder="Press Enter" />
  `
})
export class ClickCounterComponent {
  clickCount = 0;

  increment() {
    this.clickCount++;
  }

  reset() {
    this.clickCount = 0;
  }

  onEnter(event: Event) {
    const input = event.target as HTMLInputElement;
    alert(`You typed: ${input.value}`);
  }
}

Expected output: A click counter that increments on button press and triggers an alert when Enter is pressed in the input.

The $event variable contains the DOM event object. You can pass it explicitly to the handler method, or you can omit it if the method does not need event details. Key event filters like keyup.enter let you respond to specific keys.

Two-Way Binding

Two-way binding combines property binding and event binding using [(ngModel)]:

import { Component } from "@angular/core";
import { FormsModule } from "@angular/forms";

@Component({
  selector: "app-name-editor",
  standalone: true,
  imports: [FormsModule],
  template: `
    <label for="name">Name:</label>
    <input id="name" [(ngModel)]="name" placeholder="Enter your name" />
    <p>Hello, {{ name }}!</p>
  `
})
export class NameEditorComponent {
  name = "Angular Developer";
}

Expected output: An input field pre-filled with "Angular Developer". As you type, the paragraph below updates in real time.

The [(ngModel)] syntax is called "banana in a box" because of the parentheses inside the brackets. It requires FormsModule to be imported. It is syntactic sugar for [ngModel]="name" (ngModelChange)="name = $event".

Template Reference Variables

Use the hash symbol # to create a reference to a DOM element or directive:

import { Component, ViewChild, ElementRef } from "@angular/core";

@Component({
  selector: "app-focus-demo",
  standalone: true,
  template: `
    <input #myInput type="text" placeholder="I will be focused" />
    <button (click)="focusInput()">Focus Input</button>
  `
})
export class FocusDemoComponent {
  @ViewChild("myInput") inputEl!: ElementRef;

  focusInput() {
    this.inputEl.nativeElement.focus();
  }
}

Expected output: Clicking the button focuses the input field without needing document.querySelector.

Template reference variables are available in the template they are declared in. You can access them in the component class using @ViewChild with the variable name as a string.

Safe Navigation Operator

The safe navigation operator ?. prevents errors when accessing properties of null or undefined:

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

@Component({
  selector: "app-user-detail",
  standalone: true,
  template: `
    <p>Name: {{ user?.name ?? "Unknown" }}</p>
    <p>Email: {{ user?.contact?.email ?? "No email" }}</p>
    <p>Address: {{ user?.address?.city ?? "No address" }}</p>
  `
})
export class UserDetailComponent {
  user: { name: string; contact?: { email: string }; address?: { city: string } } | null = null;
}

Expected output: "Name: Unknown", "Email: No email", "Address: No address" without throwing errors because user is null.

When user is null, the expression short-circuits and returns undefined. The ?? operator provides a fallback value. This pattern is essential when dealing with API responses where nested properties may be missing.

Common Mistakes

  1. Using interpolation for boolean attributes — Writing disabled="{{ isDisabled }}" sets the attribute to the string "true" or "false", which always evaluates as truthy. Use [disabled]="isDisabled" instead.

  2. Forgetting FormsModule for ngModel — Using [(ngModel)] without importing FormsModule throws an error. Add it to the component's imports array.

  3. Event binding with wrong method signature — If the method expects $event but you omit it in the template, the method receives undefined.

  4. Property binding on innerHTML — Using [innerHTML]="userProvidedContent" can expose your app to XSS Attacks. Always sanitize or use Angular's built-in bypass only when necessary.

  5. Overcomplicating template expressions — Complex logic in templates is hard to test and debug. Move heavy computations to the component class or use pipes.

Practice Questions

  1. What is the difference between {{ }} and [ ] in Angular templates? {{ }} inserts text content into the DOM. [ ] binds to a DOM property, preserving the value type.

  2. How do you listen to a button click event? Use (click)="handlerMethod()" on the button element.

  3. What does [(ngModel)] do? It creates two-way data binding between a form input and a component property.

  4. What is a template reference variable? A hash-prefixed variable (#myVar) that references a DOM element, directive, or component in the template.

  5. How do you safely access nested object properties? Use the safe navigation operator ?. and nullish coalescing ?? to provide fallback values.

Challenge

Build a live preview component. Use two-way binding on a <textarea> and display the rendered Markdown-like preview below it. Use event binding on a "Clear" button to reset the text. Use property binding to disable the button when the textarea is empty.

FAQ

What is the banana-in-a-box syntax?

[(ngModel)] is shorthand for property binding [ngModel] plus event binding (ngModelChange). It creates two-way data binding in one expression.

Can I use multiple event bindings on one element?

Yes, you can add separate event bindings for different events on the same element, like (click) and (mouseenter).

What is the difference between DOM property and HTML attribute?

Properties are in the DOM object; attributes are in the HTML text. [src] binds to the property, which is what Angular renders.

How do I bind to a custom event from a child component?

Use @Output() in the child and (eventName) in the parent template.

Can I use JavaScript expressions in interpolation?

Yes, but only expressions that do not have side effects. Assignment, new, increment/decrement, and void are not allowed.

Mini Project

Build a ProductFilterComponent that displays a list of products with a search input. Use two-way binding on the search field, property binding for product images, event binding for the search button, and a template reference variable to focus the input on load. Filter the product list based on the search term.

import { Component } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { CommonModule } from "@angular/common";

interface Product {
  name: string;
  price: number;
  inStock: boolean;
}

@Component({
  selector: "app-product-filter",
  standalone: true,
  imports: [FormsModule, CommonModule],
  template: `
    <input #searchInput type="text" [(ngModel)]="searchTerm" placeholder="Search products..." />
    <button (click)="clearSearch()">Clear</button>
    <ul>
      <li *ngFor="let product of filteredProducts">
        {{ product.name }} - ${{ product.price }}
        <span [style.color]="product.inStock ? 'green' : 'red'">
          {{ product.inStock ? "In Stock" : "Out of Stock" }}
        </span>
      </li>
    </ul>
  `
})
export class ProductFilterComponent {
  searchTerm = "";
  products: Product[] = [
    { name: "Laptop", price: 999, inStock: true },
    { name: "Mouse", price: 25, inStock: false },
    { name: "Keyboard", price: 75, inStock: true },
  ];

  get filteredProducts() {
    return this.products.filter(p =>
      p.name.toLowerCase().includes(this.searchTerm.toLowerCase())
    );
  }

  clearSearch() {
    this.searchTerm = "";
  }
}

What's Next

Now that you understand templates, move on to directives and pipes:

Angular Directives, Angular Pipes, Angular Components

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro