Angular provides a very rich development platform for modern web applications. That richness extends into the template as much as it does into the JavaScript code areas. A very useful but potentially obscure feature of Angular templates are the Template Variable
; sometimes also referred to as Template Reference Variables
or Local References
.
What is a Template Variable?
A template variable is a variable that is created in, and identifies a component or element within, the template itself. In other words, a local reference
to some element in your template that you wish to refer to, pass around, pass back to or reference from the TypeScript component or call things on.
Template variables may refer to any one of the following in a template:
- DOM elements
- Directives
- Elements
- TemplateRefs
- Web Components
Defining a Template Variable
Creating a template variable is very easy with Angular. Simply add a unique identifier to whatever it is you wish to reference, like so:
<input type="email" placeholder="Email Address" #email>
The reference in the example here is defined by the #email
attribute added to the element. The name of the reference is simply email
so, when actually referencing the variable, drop the hash #
.
That’s all you need to define a template variable. You may in fact already be defining them in some of your templates. For example, if you use template forms, when you bind the form to ngForm
you are in fact defining a template variable for the form:
<form #form="ngForm"> <!-- ... --> </form>
The form above is referenced by the identifier form
and that reference may be used in the template or passed to methods on the component.
Directives
Elements are easy to define a template variable reference for. Directives are a little less obvious; but you have already seen how. The input example is defining a reference on an element. The form example, however, is actually defining a reference the ngForm
directive. To define a template variable for a directive, simply assign the variable to the directive.
Template Variable Scope
Template variables are available within the template they are defined in. Note that if you use any structural directive, or use <ng-template>
or <ng-container>
, those directives and elements create a child scope. Template variables defined on or in such child scopes are only accessible within those scopes.
Using a Template Variable
To use a template variable, use the identifier name. In the case of the example form above; if we were to submit that form we would pass the form to our submit method like so:
<form #form="ngForm" (ngSubmit)="submit(form)"> <!-- ... --> </form>
This is template local access to a template variable. In this case, the reference is passed into the component via a method.
The code in the component would access the FormGroup
on the ngForm
directive that is passed to the submit()
method. The example below destructures the NgForm.form
property from the passed form directive to get ahold of the actual form group:
export class Component { submit({form}) { // validate and submit form... } }
Template variables may be used strictly within the template as well. Consider the input example and the need to display validation errors in a template form:
<form #form="ngForm" (ngSubmit)="submit(form)" novalidate> <input name="name" #name="ngModel" [ngClass]="{ 'invalid': form.submitted && name.invalid }" required> <div *ngIf="form.submitted && name.invalid" class="validation-messages"> <div *ngIf="name.errors.required">Name is required</div> </div> </form>
View Children
Another way to reference template variables is through the ViewChild
decorator. Within a component, properties so decorated may access one of a number of types of view children identified in one of many ways. Elements, directives and child components may all be referenced directly via template variables within the component.
To access a specific child component of a particular type, define a variable for it in the template:
<child-component #child ...> </child-component>
And use @ViewChild('child')
in the component:
export class Component { @ViewChild('child', { static: true }) child: ChildComponent; }
Note that many children will be static
in that they do not change throughout the lifetime of the component. In some cases, you may need to update the component reference to a view child. In that case you should set static
to false
which will ensure the reference is updated in the event that the content changes. This should not generally be required for template references but there are edge cases.
Element References
When using @ViewChild
to access something from your template in the component, you may also read alternative forms or related references. For example, to get a reference to the HTML element instead of the component:
export class Component { @ViewChild('child') child: ChildComponent; @ViewChild('child', { read: ElementRef }) childElement: ElementRef; }
You could also reference any provider provided on your child component as well:
@Component({ providers: [ChildService] }) export class ChildComponent { } export class Component { @ViewChild('child') child: ChildComponent; @ViewChild('child', { read: ElementRef }) childElement: ElementRef; @ViewChild('child', { read: ChildService }) childService: ChildService; }
Template References
Template references are one of the more powerful tools in the Angular toolbox. They can be used with <ng-template>
to define child templates that may be referenced elsewhere in the component’s template, passed to child components or referenced in the component code.
A simple use case for template references, with template variables, is to allow cleaner markup in your templates while allowing alternative representations in certain areas of the template:
<grid> <row> <col class="status"> <!-- Set the template as statusTemplate, pass item as the let-item template variable --> <ng-container [ngTemplateOutlet]="statusTemplate;context:{item: item}"> </ng-container> </col> </row> <row> <col> <ng-container [ngTemplateOutlet]="iconTemplate"> </ng-container> </col> <col> <img [src]="'/categories/logos/' + item.categoryId"> </col> </row> </grid> <ng-template #instock> <fa-icon icon="check-square" size="7x" style="color: var(--green)"> </fa-icon> </ng-template> <ng-template #outofstock> <fa-icon icon="times-circle" size="7x" style="color: var(--red)"> </fa-icon> </ng-template> <ng-template #instockmsg let-item="item"> <span>This item is currently in stock!</span> </ng-template> <ng-template #outofstockmsg let-item="item"> <span class="alert">This item is out of stock.</span> </ng-template>
With the following component for support:
export class ItemStatusComponent { @Input() item: Item; @ViewChild('instock') instock: TemplateRef<any>; @ViewChild('outofstock') outofstock: TemplateRef<any>; @ViewChild('instockmsg') instockMsg: TemplateRef<any>; @ViewChild('outofstockmsg') outofstockMsg: TemplateRef<any>; get statusTemplate(): TemplateRef<any> { return this[`${this.item.status}Msg`]; } get iconTemplate(): TemplateRef<any> { return this[this.item.status]; } }
Alternative Representations
Another excellent use case for templates and template references is to contain alternative representations or content to be displayed under certain circumstances. A common example is when loading data and displaying a message while the data is loading:
<ng-container *ngIf="isLoading$ | async; else itemsList"> Loading items... </ng-container> <ng-template #itemsList> ... </ng-template>
You can also nest such container/template structures as well. Another common pattern is to display a different message when after loading is complete and you still have no data (i.e. no items were found):
<ng-container *ngIf="isLoading$ | async; else itemsList"> Loading items... </ng-container> <ng-template #itemsList> <ng-container *ngIf="items$ | async as items; else noItems"> <app-items-list [items]="items"></app-items-list> </ng-container> <ng-template #noItems> No items were found! </ng-template> </ng-template>
Simple yet Powerful
Template variables are a very simple tool in Angular templates, however, they also provide access to some of its most advanced and powerful features. They bring a level of template purity and simplicity, allowing the developer to avoid creating unnecessary properties and methods in their component. Instead, directly accessing the relevant functionality or data strictly from the template.
When combined with @ViewChild
and TemplateRef
, they bring a significant amount of power to the table. They can allow cleaner template design and better template organization where interchangeable content can be encapsulated. Further, templates can be driven in a more dynamic manner with template references.