Text interpolation
Text interpolation allows you to display dynamic values in your views. By wrapping an expression with ${}
, you can render variables, object properties, function results, and more within your HTML. This is conceptually similar to JavaScript template literals.
Template expressions
Expressions inside ${}
can perform operations such as arithmetic, function calls, or ternaries:
<p>Quick maths: ${2 + 2}</p>
<!-- Outputs "Quick maths: 4" -->
Calling functions
You can call functions defined on your view model. For example:
export class MyApp {
adder(val1: number, val2: number): number {
return parseInt(val1) + parseInt(val2);
}
}
<p>Behold mathematics, 6 + 1 = ${adder(6, 1)}</p>
<!-- Outputs "Behold mathematics, 6 + 1 = 7" -->
Using ternaries
You can also use ternary operations:
<p>${isTrue ? 'True' : 'False'}</p>
This will display either "True" or "False" depending on the boolean value of isTrue
.
Complex expressions
You can use more sophisticated expressions for dynamic content:
export class MyApp {
items = [
{ name: 'Apple', price: 1.50, category: 'fruit' },
{ name: 'Banana', price: 0.80, category: 'fruit' },
{ name: 'Carrot', price: 0.90, category: 'vegetable' }
];
}
export class MyApp {
user = {
profile: {
personal: { firstName: 'John', lastName: 'Doe' },
settings: { theme: 'dark', notifications: true }
}
};
}
<p>Status: ${isLoggedIn && user ? 'Authenticated' : 'Guest'}</p>
<p>Display: ${showDetails || showSummary ? 'Visible' : 'Hidden'}</p>
<p>Count: ${count || 0}</p>
<p>Message: ${message?.trim() || 'No message'}</p>
Optional Syntax
Aurelia supports the following optional chaining and nullish coalescing operators in templates:
??
?.
?.()
?.[]
Note that ??=
is not supported.
You can use these operators to safely handle null or undefined values:
<p>User Name: ${user?.name ?? 'Anonymous'}</p>
This helps avoid lengthy if-statements or ternary checks in your view model when dealing with potentially undefined data.
HTMLElement Interpolation
Aurelia supports passing HTMLElement objects directly to template interpolations. This allows you to dynamically create and insert DOM elements into your templates at runtime.
Creating elements with document.createElement()
document.createElement()
You can create DOM elements in your view model and bind them directly:
export class MyApp {
content = document.createElement('button');
constructor() {
this.content.textContent = 'Click me!';
this.content.addEventListener('click', () => {
alert('Button clicked!');
});
}
}
<div>${content}</div>
The button element will be directly inserted into the div, maintaining all its properties and event listeners.
Parsing HTML strings
You can also parse HTML strings and render the resulting elements:
export class MyApp {
content = Document.parseHTMLUnsafe('<button>Parsed Button</button>').documentElement;
}
<div>${content}</div>
When using Document.parseHTMLUnsafe()
, be cautious about the source of your HTML strings to avoid XSS vulnerabilities. Only use this with trusted content.
Security Considerations
When interpolating HTMLElements, be mindful of security implications:
export class MyApp {
// ✅ Safe: Creating known elements
createSafeButton() {
const button = document.createElement('button');
button.textContent = 'Safe Button'; // textContent escapes content
button.className = 'safe-class';
return button;
}
// ❌ Dangerous: Using innerHTML with user input
createUnsafeElement(userInput: string) {
const div = document.createElement('div');
div.innerHTML = userInput; // Can execute scripts!
return div;
}
// ✅ Better: Sanitize user input or use textContent
createSafeElement(userInput: string) {
const div = document.createElement('div');
div.textContent = userInput; // Escapes all HTML
return div;
}
}
Never use innerHTML with user-provided content without proper sanitization. This can lead to XSS vulnerabilities.
Dynamic element creation
This feature is particularly useful for dynamic content scenarios:
export class MyApp {
elements: HTMLElement[] = [];
addElement() {
const newElement = document.createElement('span');
newElement.textContent = `Element ${this.elements.length + 1}`;
newElement.style.color = 'blue';
this.elements.push(newElement);
}
}
<button click.trigger="addElement()">Add Element</button>
<div repeat.for="element of elements">${element}</div>
Notes on syntax
While template interpolation is powerful, there are a few limitations to keep in mind:
You cannot chain expressions using
;
or,
.You cannot use certain primitives or operators such as
Boolean
,String
,instanceof
, ortypeof
.The pipe character
|
is reserved for Aurelia value converters and cannot be used as a bitwise operator inside interpolation.
Performance Best Practices
Avoid Complex Expressions
Keep interpolation expressions simple for better performance. Complex computations should be moved to getters or methods:
<p>${items.filter(i => i.active).map(i => i.name.toUpperCase()).join(', ')}</p>
export class MyApp {
get activeItemNames() {
return this.items
.filter(i => i.active)
.map(i => i.name.toUpperCase())
.join(', ');
}
}
Array Observation Performance
Aurelia automatically observes arrays used in interpolation. For large arrays that change frequently, consider using computed getters:
export class MyApp {
private _cachedResult: string = '';
private _lastArrayLength: number = 0;
get expensiveArrayComputation() {
if (this.largeArray.length !== this._lastArrayLength) {
this._cachedResult = this.largeArray
.filter(/* complex filter */)
.reduce(/* expensive operation */, '');
this._lastArrayLength = this.largeArray.length;
}
return this._cachedResult;
}
}
Memory Considerations
When using HTMLElement interpolation, ensure proper cleanup to avoid memory leaks:
export class MyApp {
elements: HTMLElement[] = [];
detaching() {
// Clean up event listeners and references
this.elements.forEach(el => {
el.removeEventListener('click', this.handleClick);
});
this.elements = [];
}
}
Error Handling and Edge Cases
Handling Null and Undefined Values
Interpolation gracefully handles null
and undefined
values by rendering empty strings:
export class MyApp {
name: string | null = null;
data: any = undefined;
}
Error-Prone Expressions
Some expressions can throw runtime errors. Use defensive patterns:
<p>${user.profile.name}</p> <!-- Error if user or profile is null -->
<p>${items[selectedIndex].title}</p> <!-- Error if index out of bounds -->
<p>${calculateTotal()}</p> <!-- Error if method throws -->
<p>${user?.profile?.name ?? 'Anonymous'}</p>
<p>${items[selectedIndex]?.title ?? 'No item selected'}</p>
<p>${safeCalculateTotal()}</p>
Type Coercion Behavior
Interpolation converts values to strings following JavaScript coercion rules:
export class MyApp {
number = 42;
boolean = true;
array = [1, 2, 3];
object = { name: 'test' };
}
HTMLElement Edge Cases
When interpolating HTMLElements, be aware of these behaviors:
export class MyApp {
nullElement: HTMLElement | null = null;
detachedElement = document.createElement('div');
constructor() {
this.detachedElement.textContent = 'Detached';
// Element not in DOM yet
}
}
Advanced Example: Dynamic Content with Observer Updates
Here's an example showing how interpolation works with Aurelia's observer system to automatically update the view when data changes:
export class MyApp {
items = [];
constructor() {
this.items.push({ name: 'Item 1' }, { name: 'Item 2' });
}
addItem() {
this.items.push({ name: `Item ${this.items.length + 1}` });
}
}
<ul>
<li repeat.for="item of items">${item.name}</li>
</ul>
<button click.trigger="addItem()">Add Item</button>
The interpolation is automatically updated by Aurelia's array observer whenever items are added to the collection.
Last updated
Was this helpful?