Angular 19.2.0 introduces an untagged template literal in expressions feature where variables can be interpolated with backticks within an Angular expression in a template. The purpose is to facilitate string concatenations within the template of a component.
These cases are supported:
- Single interpolation
- Multiple interpolations
- Interpolation with pipe
The feature is in 19.2.0-next; please test it with a Stackblitz demo.
ng update @angular/cli --next
ng update @angular/core --next
Single Interpolation
@Component({
selector: 'app-root',
templateUrl: `./main.component.html`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
description = `${VERSION.full} - Untagged Template Literals`;
}
// main.component.html
<h1>Version {{ description }}</h1>
Before the untagged template literals feature, the “Version” static text is positioned before the expression.
// main.component.html
<h1>{{ `Version ${description}` }}</h1>
After the untagged template literals feature, the static text, “Version”, and the expression, description, are concatenated into a string. The h1 element renders "Version 19.2.0-next.0 - Untagged Template Literals".
Interpolate class
// app-user.component.css
.color-admin {
Color: red;
}
.color-user {
Color: rebeccaPurple;
}
.color-intruder {
Color: blue
}
Add some classes to change the text color based on user type.
// app-user.component.html
<!-- Template string with pipe -->
@let className = 'color-' + type();
<h2 [class]="className">{{ type() | titlecase }} Component</h2>
Before the untagged template literals feature, I declare a temporary variable to derive the class name. The class attribute is an input, and I assign the class variable to it.
// app-user.component.html
<!-- Template string with pipe -->
<h2 class="{{ `color-${type()}` }}">{{ type() | titlecase }} Component</h2>
After the untagged template literal feature, the class name is interpolated within the Angular expression, and two lines are combined into one line.
- When the user type is Admin, the class is evaluated to 'color-admin', and the text color is red.
- When the user type is User, the class is evaluated to 'color-user', and the text color is rebeccaPurple.
- When the user type is Intruder, the class is evaluated to 'color-intruder', and the text color is blue.
Interpolate style
export function getHeaderColor(type: string) {
if (type === 'admin') {
return 'red';
} else if (type === 'intruder') {
return 'blue';
} else if (type === 'user') {
return 'rebeccaPurple';
}
return 'black';
}
color = computed(() => getHeaderColor(this.type()));
// app-user.component.html
<!-- Template string with pipe -->
<h2 [style.color]="color()">{{ type() | titlecase }} Component</h2>
Before the untagged template literals feature, I assign the color computed signal to the color attribute of the style.
// app-user.component.html
<!-- Template string with pipe -->
<h2 style="{{ `color: ${color()}` }}">{{ type() | titlecase }} Component</h2>
After the untagged template literal feature, the style attribute and value are interpolated within the Angular expression. Class interpolation is more beneficial than style interpolation, according to my observation.
- When the user type is Admin, the style is evaluated to "color: red" and the text color is red.
- When the user type is User, the style is evaluated to "color: rebeccaPurple", and the text color is rebeccaPurple.
- When the user type is Intruder, the class is evaluated to "color: blue", and the text color is blue.
Multiple interpolations
@Component({
selector: 'app-root',
templateUrl: `./main.component.html`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
description = `${VERSION.full} - Untagged Template Literals`;
userName = signal('N/A');
userType = signal<"user" | "admin" | "intruder">('user');
componentType = computed(() => configs[this.userType()]);
inputs = computed(() => ({
permissions: this.componentType().permissions,
name: this.userName(),
type: this.userType(),
}));
}
// main.component.html
@let ct = componentType();
<ng-container [ngComponentOutlet]="ct.type"
[ngComponentOutletInputs]="inputs()"
#instance="ngComponentOutlet"
/>
@let permissions = componentInstance?.permissions() ?? [];
@let strPermission = permissions.join(', ');
@let numPermissions = permissions.length;
@let permissionUnit = numPermissions > 1 ? 'permissions' : 'permission';
@let delimiter = numPermissions >= 1 ? ', ' : '';
Multiple Interpolations: {{ numPermissions }} {{ permissionUnit }}{{ delimiter}}{{strPermission} }}
The componentInstance
variable is the dynamically rendered component. The permissions
variable stores the permission arrays of the rendered component. The strPermission
variable concatenates the permissions
array into a string. The permissionUnit
has the value "permissions" when the user has multiple permissions. When the user has 0 or 1 permission, the value is "permission". A comma is inserted before the permission string when users have multiple permissions.
Before the untagged template literals feature, the static text, "Multiple Interpolations" is positioned before the expressions. All the expressions, numPermissions
, permissionUnit
, delimiter
, and strPermission
are interpolated separately.
// main.component.html
@let permissions = componentInstance?.permissions() ?? [];
@let strPermission = permissions.join(', ');
@let numPermissions = permissions.length;
@let permissionUnit = numPermissions > 1 ? 'permissions' : 'permission';
@let delimiter = numPermissions >= 1 ? ', ' : '';
{{ `Multiple Interpolations: ${numPermissions} ${permissionUnit}${delimiter}${strPermission}` }}
After the untagged template literals, these variables are concatenated and interpolated between the curly braces of the Angular expression.
- When the user type is Admin, the template string is evaluated to "Multiple Interpolations: 4 permissions, create, edit, view, delete".
- When the user type is User, the template string is evaluated to "Multiple Interpolations: 1 permission, view".
- When the user type is Intruder, the template string is evaluated to "Multiple Interpolitions: 0 permission".
Interpolation with pipe
@Component({
selector: 'app-admin',
templateUrl: `app-user.component.html`,
imports: [TitleCasePipe],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AdminComponent implements Permission {
permissions = input.required<string[]>();
name = input('N/A');
type = input.required<string>();
}
The AdminComponent
component imports the TitleCasePipe
pipe to capitalize the permissions and type inputs.
Before the untagged template literals, the expression passes the signal value of type to the titleCase pipe. Then, the static text "Component" follows the expression with a space before it.
After the untagged template literals, the signal value passes to the titleCase pipe and is concatenated with the static text "Component" to form a new string inside the Angular expression.
- When the user type is Admin, the template string is evaluated to "Admin Component".
- When the user type is User, the template string is evaluated to "User Component".
- When the user type is Intruder, the template string is evaluated to "Intruder Component".
The untagged template literals feature is a convenient way to concatenate strings into a string and evaluate it within an Angular expression. Depending on an engineering team's style guide and preferences, engineers can use ES6's template string to concatenate and interpolate strings in the template.
References:
- PR: https://github.com/angular/angular/pull/59230
- Stackblitz Demo:
- https://stackblitz.com/edit/stackblitz-starters-mhffuknz?file=src%2Fmain.component.html
- https://stackblitz.com/edit/stackblitz-starters-mhffuknz?file=src%2Fapp-user.component.html
- Post by Amos Isaila: https://www.linkedin.com/feed/update/urn:li:activity:7288814996073992192/
Top comments (1)
Thank you for clear explanation, pretty useful feature is coming ;)