Angular Templates — Complete Guide to Template Syntax and Data Binding
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
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.Forgetting FormsModule for ngModel — Using
[(ngModel)]without importingFormsModulethrows an error. Add it to the component'simportsarray.Event binding with wrong method signature — If the method expects
$eventbut you omit it in the template, the method receivesundefined.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.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
What is the difference between
{{ }}and[ ]in Angular templates?{{ }}inserts text content into the DOM.[ ]binds to a DOM property, preserving the value type.How do you listen to a button click event? Use
(click)="handlerMethod()"on the button element.What does
[(ngModel)]do? It creates two-way data binding between a form input and a component property.What is a template reference variable? A hash-prefixed variable (
#myVar) that references a DOM element, directive, or component in the template.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
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