#Interpolation
A template is not HTML that Angular "fills in" with string replacement. The compiler turns it into a function that creates the DOM once and then, on every change detection pass, re-evaluates each binding and writes only the ones whose value changed. Every binding syntax in this chapter is a different instruction in that function.
Interpolation — {{ expression }} — is the simplest one: evaluate the expression against the component instance, convert the result to a string, and write it into a text node. A good mental model: the template is a live view of the component's fields. {{ name }} means "this text node always shows whatever this.name is right now".
The expression is a restricted JavaScript expression whose scope is the component instance (no this.), plus any template variables. It can't see globals: {{ Math.max(1, 2) }} fails with TS2339: Property 'Math' does not exist on type '<your component>', and {{ name = 'x' }} fails with "Parser Error: Bindings cannot contain assignments". Angular 22 templates do accept ?., ??, template literals, typeof, ** and in.
@Component({
selector: 'app-greeting',
template: `
<h1>Hello, {{ name }}!</h1>
<p>{{ items.length }} items, total {{ price * qty }}</p>
<p>{{ user?.email ?? 'no email' }}</p>
<p>[{{ nothing }}] [{{ tags }}] [{{ settings }}]</p>
<img src="/avatars/{{ id }}.png" alt="{{ name }}'s avatar" />
`,
})
export class Greeting {
name = 'Rohit';
items = ['a', 'b', 'c'];
price = 2.5;
qty = 4;
user: { email?: string } | null = null;
nothing: string | undefined = undefined;
tags = ['ng', 'ts'];
settings = { dark: true };
id = 42;
}- The
<h1>rendersHello, Rohit!— and it is one element with one text node. The compiler joins the static text and the expression into a single instruction,ɵɵtextInterpolate1("Hello, ", ctx.name, "!"), wherectxis the component instance. {{ items.length }} items, total {{ price * qty }}becomesɵɵtextInterpolate2(…)and renders3 items, total 10. Any expression works — arithmetic, property access, method calls — as long as it has no side effects.userisnull, souser?.emailshort-circuits toundefinedand??supplies the fallback:no email. Without?., strict template checking would rejectuser.emailat compile time ("Object is possibly 'null'").- The fourth line shows how values are stringified:
undefined(andnull) render as an empty string, not the word "undefined"; the array becomesng,ts(likeString(['ng', 'ts'])); the object becomes[object Object]. The real output is[] [ng,ts] [[object Object]]. - Interpolation inside an attribute is not an attribute write at all. The compiler turns
src="/avatars/{{ id }}.png"into a property binding:ɵɵdomProperty("src", ɵɵinterpolate1("/avatars/", ctx.id, ".png"), ɵɵsanitizeUrl). Note the third argument — becausesrcis a URL, the value also passes through Angular's URL sanitizer (see Angular's built-in XSS protection).
Here's the trimmed compiled output for that template (from a development build). The rf & 1 block runs once to create DOM nodes; the rf & 2 block runs on every check to update them:
function Greeting_Template(rf, ctx) {
if (rf & 1) {
ɵɵdomElementStart(0, "h1");
ɵɵtext(1);
ɵɵdomElementEnd();
// … the <p>s …
ɵɵdomElement(8, "img", 3);
}
if (rf & 2) {
ɵɵadvance();
ɵɵtextInterpolate1("Hello, ", ctx.name, "!");
ɵɵadvance(2);
ɵɵtextInterpolate2("", ctx.items.length, " items, total ", ctx.price * ctx.qty);
// …
ɵɵdomProperty("src", ɵɵinterpolate1("/avatars/", ctx.id, ".png"), ɵɵsanitizeUrl)
("alt", ɵɵinterpolate1("", ctx.name, "'s avatar"));
}
}- The create block builds the elements and empty text nodes, each with a slot index (
0,1, …). This happens once per component instance. - The update block walks the slots in order with
ɵɵadvance()and calls one instruction per binding. Each instruction compares the new value with the one it stored last time and touches the DOM only if it changed. - So "re-rendering" a component in Angular never rebuilds its HTML. It re-runs this small update function, which usually ends up writing nothing.