| |

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 .html files. 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 }}
  • new expressions — {{ 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.

FeatureTypeScriptTemplate
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 @ViewChild for 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 @ViewChild and an ngAfterViewInit.


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 $any exists: 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, @if and @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

SyntaxPurpose
{{ 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

FeatureAllowed
Property access
Method calls
Arithmetic
Ternary
Nullish coalescing
Optional chaining
Template literals
Assignments
new
; statements
++ / --
Globals (window, document)

Template Reference Variables

SyntaxRefers to
#ref on elementThe DOM element
#ref on componentThe component instance
#ref on directiveThe directive instance
#ref="exportAs"The named export

Interpolation vs Property Binding

UseExampleNotes
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

ExpressionRenders
{{ 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

PitfallProblemSolution
Expensive method callsRuns every CD cyclePrecompute in class
Impure expressionsSide effects, unpredictableKeep pure
[disabled] as interpolationString "false" is truthyUse [disabled]
Rendering objects[object Object]Access properties
Using window/documentNot in template scopeExpose via class
Assignments in {{ }}Syntax errorAssign in class
Forgetting ?.Runtime error on nullUse optional chaining
Overusing $anyDefeats type checkingFix 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

ConceptMeaning
TemplateHTML with Angular syntax
Interpolation{{ expr }} renders text
ExpressionRead-only, pure evaluation
Template reference variable#ref — handle to element/component
Attribute interpolationattr="{{ expr }}" — string only
Property binding[prop]="expr" — keeps type
Change detectionRe-runs expressions every cycle
$anyEscape 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
  • null and undefined render 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, or console
  • Keep logic in the class, not the template
  • Use $any only 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!