Angular 6 🅰️ Templates and Interpolation
A template is the HTML that a component renders. It looks like regular HTML, but Angular extends it with its own syntax — double curly braces, square brackets, parentheses, and asterisks — that connects the markup to the component’s data and behavior. Interpolation is the simplest of those extensions: it renders a component property as text inside the template using {{ }}. Everything else in Angular’s template syntax builds on the same idea — the template reads from the class, and the class never reaches into the template.
Key point: Templates are declarative. You describe what the UI should look like for a given state, and Angular keeps it in sync as state changes. Interpolation is the entry point — the first binding you learn, and the one you’ll use in nearly every template you write.
What a template is
Every component has exactly one template. It can be inline — a string inside the @Component decorator — or in a separate file referenced by templateUrl. The CLI generates a separate file by default.
import { Component } from '@angular/core';
@Component({
selector: 'app-greeting',
standalone: true,
templateUrl: './greeting.component.html',
styleUrl: './greeting.component.css'
})
export class GreetingComponent {
name = 'Alice';
}
<!-- greeting.component.html -->
<h1>Hello, {{ name }}!</h1>
The class holds name. The template reads it with {{ name }}. Angular renders <h1>Hello, Alice!</h1>. Change name and the DOM updates — no manual DOM manipulation, no innerHTML.
What the template can access:
- Component properties (public ones)
- Component methods (public ones)
- Template reference variables (
#ref) - Pipe results
- A small set of globals —
$any,undefined,null
What it cannot access: window, document, console, or anything not on the component class or explicitly exposed. Templates live in their own scope.
Why a separate template file: Editor support — syntax highlighting, autocomplete, and the Angular Language Service — works best when HTML lives in
.htmlfiles. Inline templates are fine for one-liners but become hard to read past a few lines.
Interpolation — {{ }}
Interpolation renders a TypeScript expression as text. The expression is evaluated on every change detection cycle, and the result is inserted as a string.
export class UserComponent {
firstName = 'Alice';
lastName = 'Johnson';
age = 30;
fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
<p>First: {{ firstName }}</p>
<p>Last: {{ lastName }}</p>
<p>Full: {{ fullName() }}</p>
<p>Next year: {{ age + 1 }}</p>
Anything you can write as an expression in TypeScript — property access, method calls, arithmetic, ternaries, string concatenation — works inside {{ }}. The result is coerced to a string.
What’s allowed:
- Property access —
{{ user.name }} - Method calls —
{{ fullName() }} - Arithmetic —
{{ price * quantity }} - Ternary —
{{ isActive ? 'Yes' : 'No' }} - Nullish coalescing —
{{ nickname ?? 'Guest' }} - Optional chaining —
{{ user?.address?.city }} - Template literals —
{{${first} ${last}}} - Pipe chains —
{{ price | currency }}(pipes covered later)
What’s not allowed:
- Assignments —
{{ count = 5 }} newexpressions —{{ new Date() }}(use a method or property instead);chained statements —{{ a; b }}- Increment/decrement —
{{ count++ }} - Bitwise operators with side effects
- Anything that touches globals —
{{ window.innerWidth }}
Interpolation is read-only. It observes state; it never changes it.
Why expressions and not statements: Angular needs to evaluate bindings safely and repeatedly. Expressions are pure — same inputs, same output, no side effects. That guarantee is what lets change detection run often without breaking anything.
Interpolation coerces to string
The result of {{ }} is always rendered as text. If the expression is null or undefined, Angular renders an empty string — not "null" or "undefined".
export class ProfileComponent {
bio: string | null = null;
score = 0;
isAdmin = false;
}
<p>{{ bio }}</p> <!-- renders nothing -->
<p>{{ score }}</p> <!-- renders "0" -->
<p>{{ isAdmin }}</p> <!-- renders "false" -->
Objects do not render usefully — {{ user }} produces [object Object]. Use property access or a pipe instead.
Why empty string for null/undefined: Rendering
"null"on screen is almost never what you want. Angular’s choice keeps templates clean when data hasn’t loaded yet.
Expressions run on every change detection cycle
Interpolation isn’t evaluated once. Angular re-evaluates every binding every time change detection runs — which can be many times per second. That has two consequences.
First: Keep expressions cheap. A simple property access is fine. A method that does heavy computation or triggers an HTTP call is not.
Second: Expressions must be pure. No side effects — no assignments, no logging, no mutation. Angular assumes the same inputs always produce the same output.
export class BadComponent {
items = [1, 2, 3];
// ❌ Called on every CD cycle — expensive and impure
getExpensiveTotal(): number {
console.log('computing...'); // side effect
return this.items.reduce((a, b) => a + b, 0);
}
}
<p>{{ getExpensiveTotal() }}</p>
Every keystroke, mouse move, timer tick — Angular calls getExpensiveTotal. On a list of thousands of items, this destroys performance.
Better: Precompute the value as a property, or use a computed signal (covered in later chapters).
Why Angular calls expressions so often: Change detection is how Angular keeps the DOM in sync. It compares the current rendered value to the last one and updates if they differ. That requires re-evaluating bindings on every cycle. Impure or expensive expressions break this model.
Template expressions vs TypeScript expressions
Template expressions look like TypeScript, but they’re a restricted subset. The differences matter when you’re debugging.
| Feature | TypeScript | Template |
|---|---|---|
| Property access | ✅ | ✅ |
| Method calls | ✅ | ✅ |
| Arithmetic | ✅ | ✅ |
| Ternary | ✅ | ✅ |
| Optional chaining | ✅ | ✅ |
| Nullish coalescing | ✅ | ✅ |
| Assignments | ✅ | ❌ |
new | ✅ | ❌ |
; statements | ✅ | ❌ |
++ / -- | ✅ | ❌ |
Globals (window, Math.random — see note) | ✅ | ❌ |
typeof | ✅ | ⚠️ (limited) |
Math and Date constructors are not available directly. Use a component method or property.
Why restricted: Template expressions are compiled to JavaScript that Angular runs in a controlled sandbox. Restricting syntax keeps them safe, predictable, and analyzable at build time — so template type-checking can catch errors before runtime.
Template reference variables
A template reference variable (#name) gives you a handle to a DOM element, component, or directive from within the template.
<input #emailInput type="email" placeholder="Email">
<button (click)="submit(emailInput.value)">Submit</button>
#emailInput refers to the <input> element. emailInput.value reads its current value. The variable is scoped to the template — accessible anywhere below its declaration.
For a component, #ref gives you the component instance:
<app-user-card #card [userId]="1"></app-user-card>
<p>{{ card.user?.name }}</p>
card is the UserCardComponent instance, and card.user reads its property.
Use cases:
- Reading form input values
- Calling methods on child components
- Passing element references to methods
- Using with
@ViewChildfor programmatic access (later chapter)
Why reference variables matter: They let templates refer to elements and components without component class boilerplate. For simple cases — reading an input’s value on click — a reference variable is cleaner than a
@ViewChildand anngAfterViewInit.
Interpolation inside attributes
{{ }} works inside attribute values, not just text content.
<img src="{{ imageUrl }}" alt="{{ imageAlt }}">
<a href="/users/{{ userId }}">Profile</a>
<button title="{{ tooltip }}">Hover</button>
Angular interpolates the attribute value the same way it interpolates text. imageUrl changes, and the src attribute updates.
But — for many attributes, property binding is preferred. Interpolation sets the attribute as a string; property binding sets the DOM property directly, which is faster and handles non-string values (booleans, objects) correctly.
<!-- Interpolation — fine for strings -->
<img src="{{ imageUrl }}">
<!-- Property binding — preferred -->
<img [src]="imageUrl">
<button [disabled]="isLoading">Save</button>
[disabled]="isLoading" sets the boolean disabled property. disabled="{{ isLoading }}" would set the attribute to the string "false" — which is still truthy — a classic bug.
Why property binding is preferred: HTML attributes are always strings. DOM properties can be any type. Property binding talks to the property directly, so booleans, numbers, and objects behave correctly. Interpolation is a string conversion.
The $any escape hatch
Occasionally a template expression is rejected by the type checker even though you know it’s valid. $any(expr) disables type checking for that expression.
<p>{{ $any(user).customField }}</p>
Use it sparingly — it defeats the purpose of template type-checking. If you find yourself reaching for $any, it usually means the type is wrong somewhere else.
Why
$anyexists: Migration and edge cases. It’s an escape hatch, not a tool.
A full example
A small profile card using interpolation, a method, arithmetic, a ternary, and optional chaining.
The class:
import { Component } from '@angular/core';
interface Address {
city: string;
}
interface User {
firstName: string;
lastName: string;
age: number;
address?: Address;
nickname: string | null;
}
@Component({
selector: 'app-profile-card',
standalone: true,
templateUrl: './profile-card.component.html',
styleUrl: './profile-card.component.css'
})
export class ProfileCardComponent {
user: User = {
firstName: 'Alice',
lastName: 'Johnson',
age: 30,
address: { city: 'Lisbon' },
nickname: null
};
fullName(): string {
return `${this.user.firstName} ${this.user.lastName}`;
}
get isAdult(): boolean {
return this.user.age >= 18;
}
}
The template:
<div class="card">
<h2>{{ fullName() }}</h2>
<p>Nickname: {{ user.nickname ?? 'none' }}</p>
<p>City: {{ user.address?.city ?? 'unknown' }}</p>
<p>Age next year: {{ user.age + 1 }}</p>
<p>Status: {{ isAdult ? 'Adult' : 'Minor' }}</p>
</div>
The styles:
.card {
border: 1px solid #ddd;
padding: 1rem;
border-radius: 8px;
}
Every binding reads from the class. The template has no logic of its own — it just displays what the class exposes.
Why this shape: Interpolation is pure display. If the template starts needing conditionals,
@ifand@for(next chapters) take over. Interpolation handles the text.
Complete Example Session
# ============================================
# PART 1: GENERATE A COMPONENT
# ============================================
ng generate component profile-card
# [ CREATE src/app/profile-card/profile-card.component.ts ]
# [ CREATE src/app/profile-card/profile-card.component.html ]
# [ CREATE src/app/profile-card/profile-card.component.css ]
# [ CREATE src/app/profile-card/profile-card.component.spec.ts ]
# ============================================
# PART 2: WRITE THE CLASS
# ============================================
cat > src/app/profile-card/profile-card.component.ts << 'EOF'
import { Component } from '@angular/core';
@Component({
selector: 'app-profile-card',
standalone: true,
templateUrl: './profile-card.component.html',
styleUrl: './profile-card.component.css'
})
export class ProfileCardComponent {
firstName = 'Alice';
lastName = 'Johnson';
age = 30;
nickname: string | null = null;
city: string | undefined = 'Lisbon';
fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
EOF
# ============================================
# PART 3: WRITE THE TEMPLATE
# ============================================
cat > src/app/profile-card/profile-card.component.html << 'EOF'
<h2>{{ fullName() }}</h2>
<p>Nickname: {{ nickname ?? 'none' }}</p>
<p>City: {{ city ?? 'unknown' }}</p>
<p>Age next year: {{ age + 1 }}</p>
<p>Adult: {{ age >= 18 ? 'Yes' : 'No' }}</p>
EOF
# ============================================
# PART 4: ADD A TEMPLATE REFERENCE VARIABLE
# ============================================
cat > src/app/email-form/email-form.component.ts << 'EOF'
import { Component } from '@angular/core';
@Component({
selector: 'app-email-form',
standalone: true,
template: `
<input #email type="email" placeholder="Email">
<button (click)="submit(email.value)">Submit</button>
<p>Last submitted: {{ lastEmail || '—' }}</p>
`
})
export class EmailFormComponent {
lastEmail = '';
submit(value: string): void {
this.lastEmail = value;
}
}
EOF
# ============================================
# PART 5: VIEW IN THE BROWSER
# ============================================
ng serve
# [ Local: http://localhost:4200/ ]
Quick Reference
Interpolation Syntax
| Syntax | Purpose |
|---|---|
{{ expr }} | Render expression as text |
{{ a + b }} | Arithmetic |
{{ cond ? x : y }} | Ternary |
{{ a ?? b }} | Nullish coalescing |
{{ a?.b }} | Optional chaining |
{{ fn() }} | Method call |
{{ a | pipe }} | Pipe (later chapter) |
Allowed in Templates
| Feature | Allowed |
|---|---|
| Property access | ✅ |
| Method calls | ✅ |
| Arithmetic | ✅ |
| Ternary | ✅ |
| Nullish coalescing | ✅ |
| Optional chaining | ✅ |
| Template literals | ✅ |
| Assignments | ❌ |
new | ❌ |
; statements | ❌ |
++ / -- | ❌ |
Globals (window, document) | ❌ |
Template Reference Variables
| Syntax | Refers to |
|---|---|
#ref on element | The DOM element |
#ref on component | The component instance |
#ref on directive | The directive instance |
#ref="exportAs" | The named export |
Interpolation vs Property Binding
| Use | Example | Notes |
|---|---|---|
| Text | <p>{{ name }}</p> | String output |
| Attribute | <img src="{{ url }}"> | Works, string only |
| Property | <img [src]="url"> | Preferred |
| Boolean prop | [disabled]="flag" | Correct types |
Null / Undefined Behavior
| Expression | Renders |
|---|---|
{{ null }} | empty string |
{{ undefined }} | empty string |
{{ 0 }} | 0 |
{{ false }} | false |
{{ user }} | [object Object] |
Best Practices
✅ Do This:
<!-- Keep expressions simple -->
<p>{{ userName }}</p> <!-- ✅ -->
<!-- Precompute expensive values -->
<p>{{ total }}</p> <!-- ✅ not {{ computeTotal() }} -->
<!-- Use optional chaining for maybe-undefined data -->
<p>{{ user?.name ?? 'Guest' }}</p> <!-- ✅ -->
<!-- Use property binding for non-strings -->
<button [disabled]="isLoading">Save</button> <!-- ✅ -->
<!-- Use reference variables for simple input reads -->
<input #email><button (click)="save(email.value)"> <!-- ✅ -->
<!-- Keep templates focused on display -->
<!-- Move logic into the class <!-- ✅ -->
❌ Don’t Do This:
<!-- Don't call expensive methods in interpolation -->
<p>{{ getItemsTotal() }}</p> <!-- ❌ runs every CD -->
<!-- Don't use interpolation for booleans -->
<button disabled="{{ isLoading }}">Save</button> <!-- ❌ "false" is truthy -->
<!-- Don't touch globals -->
<p>{{ window.innerWidth }}</p> <!-- ❌ not accessible -->
<!-- Don't assign in the template -->
<p>{{ count = 5 }}</p> <!-- ❌ syntax error -->
<!-- Don't render objects directly -->
<p>{{ user }}</p> <!-- ❌ [object Object] -->
<!-- Don't reach for $any without reason -->
<p>{{ $any(x).foo }}</p> <!-- ⚠️ fix the type -->
<!-- Don't put business logic in the template -->
<p>{{ items.filter(i => i.active).length }}</p> <!-- ❌ move to class -->
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
| Expensive method calls | Runs every CD cycle | Precompute in class |
| Impure expressions | Side effects, unpredictable | Keep pure |
[disabled] as interpolation | String "false" is truthy | Use [disabled] |
| Rendering objects | [object Object] | Access properties |
Using window/document | Not in template scope | Expose via class |
Assignments in {{ }} | Syntax error | Assign in class |
Forgetting ?. | Runtime error on null | Use optional chaining |
Overusing $any | Defeats type checking | Fix the type |
Real-World Examples
1. Render a name
<h1>Hello, {{ name }}!</h1>
2. Render a computed full name
<h2>{{ fullName() }}</h2>
3. Guard against null
<p>{{ nickname ?? 'Anonymous' }}</p>
4. Optional chaining
<p>{{ user?.address?.city }}</p>
5. Arithmetic
<p>Total: {{ price * quantity }}</p>
6. Ternary
<p>{{ isActive ? 'Active' : 'Inactive' }}</p>
7. Interpolate an attribute
<img src="{{ avatarUrl }}" alt="{{ userName }}">
8. Property binding instead
<img [src]="avatarUrl" [alt]="userName">
9. Template reference variable
<input #email>
<button (click)="save(email.value)">Save</button>
10. Reference a child component
<app-chart #chart [data]="sales"></app-chart>
<p>Points: {{ chart.pointCount }}</p>
11. Method call in interpolation
<p>{{ formatDate(createdAt) }}</p>
12. Pipe in interpolation (preview)
<p>{{ price | currency:'USD' }}</p>
13. Inline template for tiny components
@Component({
selector: 'app-badge',
standalone: true,
template: `<span class="badge">{{ label }}</span>`
})
export class BadgeComponent {
label = 'New';
}
14. Multiple bindings in one template
<h2>{{ title }}</h2>
<p>By {{ author }}</p>
<p>Published {{ year }}</p>
15. Escape hatch for migration
<p>{{ $any(legacy).customProp }}</p>
Visual: Interpolation Flow
┌──────────────────────────────────────────────┐
│ Class │
│ │
│ name = 'Alice' │
│ age = 30 │
│ │
└──────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Template │
│ │
│ <h1>Hello, {{ name }}!</h1> │
│ <p>Age: {{ age }}</p> │
│ │
└──────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Rendered DOM │
│ │
│ <h1>Hello, Alice!</h1> │
│ <p>Age: 30</p> │
│ │
└──────────────────────────────────────────────┘
Visual: Change Detection Loop
┌──────────────────────────────────────────────┐
│ User action / event / timer │
│ │ │
│ ▼ │
│ Change detection runs │
│ │ │
│ ▼ │
│ Re-evaluate every {{ expr }} │
│ │ │
│ ▼ │
│ Compare to previous value │
│ │ │
│ ▼ │
│ If different — update DOM │
│ │
│ Loop repeats on every CD cycle │
│ │
└──────────────────────────────────────────────┘
Visual: Template Scope
┌──────────────────────────────────────────────┐
│ Accessible in template │
│ │
│ • Component properties │
│ • Component methods │
│ • Template reference variables (#ref) │
│ • Pipe results │
│ • A few globals ($any, undefined, null) │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ NOT accessible in template │
│ │
│ • window / document │
│ • console │
│ • Math / Date constructors │
│ • Anything not exposed by the class │
│ │
└──────────────────────────────────────────────┘
Visual: Interpolation vs Property Binding
┌──────────────────────────────────────────────┐
│ Interpolation │
│ │
│ <img src="{{ url }}"> │
│ │
│ → sets the src ATTRIBUTE │
│ → always a string │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Property Binding │
│ │
│ <img [src]="url"> │
│ │
│ → sets the src PROPERTY │
│ → keeps the original type │
│ │
│ Use this for booleans, numbers, objects │
│ │
└──────────────────────────────────────────────┘
Summary
| Concept | Meaning |
|---|---|
| Template | HTML with Angular syntax |
| Interpolation | {{ expr }} renders text |
| Expression | Read-only, pure evaluation |
| Template reference variable | #ref — handle to element/component |
| Attribute interpolation | attr="{{ expr }}" — string only |
| Property binding | [prop]="expr" — keeps type |
| Change detection | Re-runs expressions every cycle |
$any | Escape hatch — disables type check |
Key takeaways:
- Templates are declarative HTML that read from the component class
- Interpolation (
{{ }}) renders expressions as text - Expressions are read-only — no assignments, no
new, no statements - Expressions run on every change detection cycle — keep them cheap and pure
nullandundefinedrender as empty string, not"null"- Template reference variables (
#ref) give the template handles to elements and components - Property binding (
[prop]="expr") is preferred over attribute interpolation for non-strings - Templates can only access class members and a few globals — no
window,document, orconsole - Keep logic in the class, not the template
- Use
$anyonly as a last resort
Remember: Interpolation is where Angular starts to feel like Angular. The template displays what the class exposes; the class never reaches into the template. Keep expressions simple and pure, prefer property binding for non-strings, and let the framework handle the DOM updates. Everything else — @if, @for, event binding, pipes — builds on this foundation.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!