Angular Template Reference Variables

Examples of how to use template reference variables to reference DOM elements, components and directives in a template.

Updated March 2018, Angular version 5.1

DOM Elements

The hash symbol (#) is used to declare a template reference variable.

A variable called refVar is declared on a textarea element (in green) and the properties and methods of the DOM element are accessed (in yellow).

text-area-one.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-text-area-one',
  template: `
    <header>Change or highlight the text below to see the values change.</header>
    <p>
      <textarea #refVar
                (keyup)="0"
                (select)="0"
                cols="25"
                rows="3">Change or highlight some of this text</textarea>
    </p>
    <div>textLength: {{refVar.textLength}}</div>
    <div>selectionStart: {{refVar.selectionStart}}</div>
    <div>selectionEnd: {{refVar.selectionEnd}}</div>
    <p>
      <button (click)="refVar.select()">
        Select All
      </button>
      <button (click)="refVar.value = ''"
              [disabled]="refVar.value === ''">
        Clear
      </button>
    </p>
  `
})
export class TextAreaOneComponent {
}

We can read properties

{{refVar.textLength}}

[disabled]="refVar.value === ''"

write to properties

(click)="refVar.value = ''"

and call methods

(click)="refVar.select()"

Note that all the functionality is achieved using the template reference variable. There is no logic in the component class.

This is the output from the component.

ref- Prefix

The ref- prefix (in green) is an alternative to the hash symbol. ref-isReadOnly is equivalent to #isReadOnly.

text-area-two.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-text-area-two',
  template: `
    <header>Use the checkboxto make the text area editable or read only.</header>
    <div>
      <textarea #textArea
                [readonly]="isReadOnly.checked"
                cols="25"
                rows="3">one two three four</textarea>
    </div>
    <label>
      <input ref-isReadOnly type="checkbox" (change)="0">
      Read only
    </label>
    <p>
      <button (click)="logMessage(textArea, isReadOnly)">Message</button>
    </p>
    <p>{{message}}</p>
  `
})
export class TextAreaTwoComponent {
  message: string;

  logMessage(textArea: HTMLTextAreaElement,
             readOnly: HTMLInputElement): void {

    this.message = `This text "${textArea.value}" is
                    ${readOnly.checked ? 'read only' : 'editable'}`;
  }
}

This is the result.

Variable Scope

Template reference variables are hoisted and can be accessed anywhere in the template. Here the isReadOnly variable is accessed before and after it is declared.

text-area-two.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-text-area-two',
  template: `
    <header>Use the checkboxto make the text area editable or read only.</header>
    <div>
      <textarea #textArea
                [readonly]="isReadOnly.checked"
                cols="25"
                rows="3">one two three four</textarea>
    </div>
    <label>
      <input ref-isReadOnly type="checkbox" (change)="0">
      Read only
    </label>
    <p>
      <button (click)="logMessage(textArea, isReadOnly)">Message</button>
    </p>
    <p>{{message}}</p>
  `
})
export class TextAreaTwoComponent {
  message: string;

  logMessage(textArea: HTMLTextAreaElement,
             readOnly: HTMLInputElement): void {

    this.message = `This text "${textArea.value}" is
                    ${readOnly.checked ? 'read only' : 'editable'}`;
  }
}

Issue with Structural Directives

Take care when using template reference variables with structural directives (*ngIf, *ngFor etc).

For example, this code

conditional.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-conditional',
  template: `
    <p #para *ngIf="true">
      This is displayed because ngIf is true
    </p>
    but #para is still undefined: {{para === undefined}}
  `,
})
export class ConditionalComponent {
}

produces this output in development mode.

This is displayed because ngIf is true

but #para is still undefined: true

#para is undefined even though *ngIf evaluates to true. To understand why, see this blog post.

During a production build, the Angular compiler will report the following error:

Property 'para' does not exist on type 'ConditionalComponent'.

Object Types

The types of these two template reference variables are HTMLTextAreaElement and HTMLInputElement, and are documented on MDN Web Docs.

text-area-two.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-text-area-two',
  template: `
    <header>Use the checkboxto make the text area editable or read only.</header>
    <div>
      <textarea #textArea
                [readonly]="isReadOnly.checked"
                cols="25"
                rows="3">one two three four</textarea>
    </div>
    <label>
      <input ref-isReadOnly type="checkbox" (change)="0">
      Read only
    </label>
    <p>
      <button (click)="logMessage(textArea, isReadOnly)">Message</button>
    </p>
    <p>{{message}}</p>
  `
})
export class TextAreaTwoComponent {
  message: string;

  logMessage(textArea: HTMLTextAreaElement,
             readOnly: HTMLInputElement): void {

    this.message = `This text "${textArea.value}" is
                    ${readOnly.checked ? 'read only' : 'editable'}`;
  }
}

Note

This example is to illustrate the object type of the variables. In a real application, a better solution would be to pass in only those values that are needed. For example:

logMessage(textArea.value, isReadOnly.checked)

logMessage(text: string, readOnly: boolean): void {
    this.message = `This text "${text}" is ${readOnly ? 'read only' : 'editable'}`;
}

This will keep details of the DOM element out of the component class, which is considered good practice.

Components

Template reference variables can be used to access public properties and methods of a component class.

Take this timer component which has several public methods.

timer.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-timer',
  template: `
    <h1>
      {{elapsedTime | date:'HH:mm:ss'}}
      <span class="half-size">{{elapsedTime | date:'S'}}</span>
    </h1>
  `
})
export class TimerComponent {
  private totalMillis = 0;
  private intervalId: number = null;

  get elapsedTime(): Date {
    return new Date(this.totalMillis);
  }

  get isRunning(): number {
    return this.intervalId;
  }

  get isTimeSet(): number {
    return this.totalMillis;
  }

  start(): void {
    const startMillis = this.nowMillis;
    this.intervalId = window.setInterval(() => {
      this.totalMillis = this.nowMillis - startMillis;
    }, 100);
  }

  stop(): void {
    if (this.intervalId) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  }

  reset(): void {
    this.stop();
    this.totalMillis = 0;
  }

  private get nowMillis(): number {
    return new Date().getTime();
  }
}

We use a template reference variable on the <app-timer> custom element to execute the methods on the component class.

stopwatch.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-stopwatch',
  template: `
    <app-timer #timer></app-timer>
    <button (click)="timer.start()" [disabled]="timer.isTimeSet">Start</button>
    <button (click)="timer.stop()" [disabled]="!timer.isRunning">Stop</button>
    <button (click)="timer.reset()" [disabled]="!timer.isTimeSet">Reset</button>
  `
})
export class StopwatchComponent {
}

And here it is.



We can also use the same component in this typing test example.

typing-test.component.ts
import { Component } from '@angular/core';
import { TimerComponent } from './timer.component';

@Component({
  selector: 'app-typing-test',
  template: `
    <header>Type the movie quote in the box as fast as you can.</header>
    <h2 #text>{{phrase}}</h2>
    <textarea #answer
      (keydown)="timer.isRunning || timer.start()"
      (keyup)="checkAnswer(answer.value, timer)"
      placeholder="Type the phrase here"
      cols="50" rows="5"></textarea>
    <div>
      <button (click)="answer.value=''; next()">Next</button>
    </div>
    <div [hidden]="answer.value !== text.innerText">
      <app-timer #timer></app-timer>
    </div>
  `
})
export class TypingTestComponent {
  private phrases: string[] = [
    'We\'re gonna need a bigger boat.',
    'What doesn\'t kill you makes you stranger.',
    'The first rule of project mayhem is you do not ask questions.',
    'As far back as I can remember, I always wanted to be a gangster.',
    'I want you to deal with your problems by becoming rich!'
  ];

  private position = 0;

  checkAnswer(answer: string, timer: TimerComponent): void {
    if (answer === this.phrase) {
      timer.stop();
    }
  }

  next(): void {
    this.position = this.position === this.phrases.length - 1 ? 0 : this.position + 1;
  }

  get phrase(): string {
    return this.phrases[this.position];
  }
}

Directives

We can do the same with custom attribute directives too. Here we assign appTextSelector to the quote variable and access the public properties and methods of the directive class.

text-snippets.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-text-selector',
  template: `
    <header [hidden]="quote.snippets.length">Select some of the text below.</header>
    <header [hidden]="!quote.snippets.length">Select some more to add to the list.</header>
    <p appTextSelector
       #quote="appTextSelector"
       (textSelected)="onSelected($event)">
      {{movieQuote}}
    </p>
    <p>Last selection: <em>{{text || 'none'}}</em></p>
    <div>
      <a [hidden]="!quote.snippets.length"
         (click)="quote.clear(); false" href="">
        Clear
      </a>
      <ol>
        <li *ngFor="let snippet of quote.snippets">{{snippet}}</li>
      </ol>
    </div>
  `,
  styles: [`
    em {
      color: Green;
      font-style: normal;
    }
  `]
})
export class TextSnippetComponent {
  movieQuote = `
      Didn’t see the first shark for about a half-hour. Tiger. 13-footer.
      You know how you know that in the water, Chief? You can tell by lookin’
      from the dorsal to the tail. What we didn’t know, was that our bomb
      mission was so secret, no distress signal had been sent. They didn’t
      even list us overdue for a week. Very first light, Chief, sharks come
      cruisin’ by, so we formed ourselves into tight groups. It was sorta
      like you see in the calendars, you know the infantry squares in the
      old calendars like the Battle of Waterloo and the idea was the shark
      come to the nearest man, that man he starts poundin’ and hollerin’
      and sometimes that shark he go away… but sometimes he wouldn’t go away.`;

  text: string;

  onSelected(text: string) {
    this.text = text;
  }
}

But first we must export the directive like this.

text-snippets.directive.ts
import { Directive, EventEmitter, HostListener, Output } from '@angular/core';

@Directive({
  selector: '[appTextSelector]',
  exportAs: 'appTextSelector'
})
export class TextSnippetDirective {

  @Output() textSelected = new EventEmitter<string>();

  private _snippets: string[] = [];

  @HostListener('mouseup')
  onSelected() {
    const text = document.getSelection().toString();
    if (text) {
      this._snippets.push(text);
      this.textSelected.emit(text);
    }
  }

  get snippets(): string[] {
    return this._snippets;
  }

  clear(): void {
    this._snippets = [];
  }
}

Here is the application.