Write Angular templates, display values with interpolation, and understand what template expressions can and cannot do.

Bishal Biswas
Self Learner
Templates are HTML with extras
An Angular template is HTML, plus a small syntax for reading data and reacting to events.
@Component({
selector: 'app-greeting',
template: `
<h1>Hello, {{ name }}</h1>
<p>You have {{ messageCount }} messages.</p>
`,
})
export class Greeting {
protected readonly name = 'Asha';
protected readonly messageCount = 3;
}
The double braces are interpolation. Angular evaluates what is inside against the component and puts the result in the DOM as text.
Inline or separate file
// inline, good for short templates
@Component({
template: `<p>{{ name }}</p>`,
})
// separate file, better once it grows
@Component({
templateUrl: './greeting.html',
})
Use backticks for inline templates so they can span lines. Move to a file at around twenty lines, when the component class starts getting lost below it.
What goes inside the braces
Interpolation takes a template expression. It looks like JavaScript but is deliberately restricted.
{{ name }}
{{ user.email }}
{{ items.length }}
{{ price * quantity }}
{{ firstName + ' ' + lastName }}
{{ isActive ? 'Yes' : 'No' }}
{{ getLabel() }}
{{ count() }} <!-- calling a signal -->
{{ user?.address?.city }}
{{ nickname ?? 'Anonymous' }}
The result is converted to a string. Objects become [object Object], so use the json pipe when debugging:
<pre>{{ user | json }}</pre>
What is not allowed
Template expressions cannot have side effects, and several JavaScript operators are banned.
{{ count = 5 }} <!-- no assignment -->
{{ count++ }} <!-- no increment -->
{{ new Date() }} <!-- no new -->
{{ a; b }} <!-- no chaining with ; -->
{{ x || y }} <!-- allowed, but ?? is usually better -->
{{ typeof x }} <!-- not supported -->
{{ window.innerWidth }} <!-- no global scope -->
Expressions are evaluated against the component instance only. There is no window, document or console. If a template needs something global, expose it from the class.
export class Layout {
protected readonly width = signal(window.innerWidth);
}
Do not call expensive methods
An expression can be evaluated many times. Anything expensive inside one is a performance problem.
<!-- runs on every check -->
<p>{{ calculateTotal() }}</p>
// better: a computed signal, recalculated only when its inputs change
protected readonly total = computed(() =>
this.items().reduce((sum, i) => sum + i.price, 0)
);
<p>{{ total() }}</p>
Calling a signal is cheap and is exactly how they are meant to be used. Calling an ordinary method that does real work is not.
Safe navigation
<p>{{ user.address.city }}</p> <!-- throws if address is undefined -->
<p>{{ user?.address?.city }}</p> <!-- renders nothing instead -->
?. is essential for data that arrives asynchronously, since the template renders before the response lands.
Escaping and safety
Angular escapes interpolated values. This is the reason cross site scripting is hard to introduce by accident.
protected readonly comment = '<script>alert(1)</script>';
<p>{{ comment }}</p>
That renders the tags as visible text. Nothing executes.
Binding to innerHTML sanitises rather than escapes, removing dangerous parts while keeping safe markup:
<div [innerHTML]="comment"></div>
Only bypass this when you genuinely control the content, and understand the risk. The security topic covers it properly.
Comments and whitespace
<!-- HTML comments are stripped from the build -->
Angular collapses insignificant whitespace by default. Turn it off per component if whitespace matters, such as inside a <pre>:
@Component({
preserveWhitespaces: true,
...
})
Template type checking
With strictTemplates on, templates are type checked against the class.
export class Profile {
protected readonly user = signal<{ name: string } | null>(null);
}
{{ user().name }} <!-- error: user() may be null -->
{{ user()?.name }} <!-- fine -->
@if (user(); as u) {
{{ u.name }} <!-- fine, narrowed to non-null -->
}
A misspelt property is a build error rather than a blank space in the browser. This is one of Angular’s genuinely strong points, and it only works if you leave strictTemplates enabled.
Multi-line and formatting
<p>
{{ user().firstName }}
{{ user().lastName }}
</p>
Interpolation can span lines, though long expressions are usually better as a computed signal in the class. Templates are easier to read when they contain almost no logic.
Quick recap
- Templates are HTML plus a restricted expression syntax.
{{ }}interpolates a value and converts it to a string.- Expressions cannot assign, increment or use
new, and have no access to globals. - Calling a signal in a template is cheap, calling an expensive method is not.
- Use
?.for data that has not arrived yet. - Interpolated values are escaped, so injected markup renders as text.
strictTemplatesturns template typos into build errors, so leave it on.
import { Component, signal, computed } from '@angular/core';
import { JsonPipe } from '@angular/common';
@Component({
selector: 'app-greeting',
imports: [JsonPipe],
template: `
<h1>Hello, {{ name }}</h1>
<p>{{ firstName + ' ' + lastName }}</p>
<p>{{ isActive ? 'Active' : 'Inactive' }}</p>
<p>{{ nickname ?? 'Anonymous' }}</p>
<!-- safe navigation for data that has not arrived yet -->
<p>{{ user()?.city }}</p>
<!-- narrowing with `as` removes the null from the type -->
@if (user(); as u) { <p>{{ u.city }}</p> }
<!-- calling a SIGNAL is cheap. Calling a heavy method is not. -->
<p>Total: {{ total() }}</p>
<!-- json pipe for debugging, since objects stringify to [object Object] -->
<pre>{{ user() | json }}</pre>
<!-- interpolated values are ESCAPED, so this renders as visible text -->
<p>{{ comment }}</p>
`,
})
export class Greeting {
protected readonly name = 'Asha';
protected readonly firstName = 'Asha';
protected readonly lastName = 'Biswas';
protected readonly isActive = true;
protected readonly nickname: string | null = null;
protected readonly comment = '<script>alert(1)</script>'; // rendered as text
protected readonly user = signal<{ city: string } | null>(null);
protected readonly items = signal([{ price: 10 }, { price: 32 }]);
// Prefer a computed signal over a method call in the template
protected readonly total = computed(() =>
this.items().reduce((sum, i) => sum + i.price, 0));
}
// NOT allowed in template expressions: assignment, ++, new, ;, typeof,
// and there is no window, document or console. Expose those from the class.
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready