Template Reference Variables and Host Binding

๐Ÿ“‹ Table of Contents โ–พ
  1. Template Reference Variables
  2. Common Mistakes

Template reference variables (#varName) create named references to DOM elements and component instances within a template โ€” enabling the template to pass references to event handlers or access component APIs without going through the component class. @HostBinding and @HostListener (or their functional equivalents host metadata) let a directive or component bind to its own host element’s properties and events, making directives that manipulate the host element clean and encapsulated.

Template Reference Variables

@Component({
  selector:   'app-post-search',
  standalone:  true,
  imports:    [FormsModule],
  template: `
    <!-- #searchInput creates a reference to the input DOM element โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -->
    <input #searchInput
           type="text"
           [(ngModel)]="query"
           placeholder="Search...">

    <!-- Pass the reference directly in the template โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -->
    <button (click)="onSearch(searchInput.value)">Search</button>
    <button (click)="searchInput.focus()">Focus Input</button>
    <p>Character count: {{ searchInput.value.length }}</p>

    <!-- Reference to a child component instance โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -->
    <app-post-list #postList />
    <button (click)="postList.refresh()">Refresh List</button>
  `,
})
export class PostSearchComponent {
  query = '';
  onSearch(value: string): void { console.log('Searching:', value); }
}

// โ”€โ”€ @HostBinding and @HostListener โ€” directive modifying its host โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import { Directive, HostBinding, HostListener, Input, signal } from '@angular/core';

@Directive({
  selector: '[appHighlight]',   // applied as: <div appHighlight>
  standalone: true,
})
export class HighlightDirective {
  @Input('appHighlight') highlightColor = 'yellow';  // <div appHighlight="pink">

  private isHovered = signal(false);

  // โ”€โ”€ @HostBinding โ€” binds to a property of the host element โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  @HostBinding('style.backgroundColor')
  get backgroundColor(): string {
    return this.isHovered() ? this.highlightColor : 'transparent';
  }

  @HostBinding('style.cursor') cursor = 'pointer';
  @HostBinding('class.highlighted') isHighlighted = false;

  // โ”€โ”€ @HostListener โ€” listens for events on the host element โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  @HostListener('mouseenter')
  onMouseEnter(): void {
    this.isHovered.set(true);
    this.isHighlighted = true;
  }

  @HostListener('mouseleave')
  onMouseLeave(): void {
    this.isHovered.set(false);
    this.isHighlighted = false;
  }

  @HostListener('click', ['$event'])
  onClick(event: MouseEvent): void {
    console.log('Highlighted element clicked at:', event.clientX, event.clientY);
  }
}

// โ”€โ”€ Modern alternative: host metadata in @Component/@Directive โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@Directive({
  selector: '[appHighlight]',
  standalone: true,
  host: {
    '(mouseenter)': 'onMouseEnter()',
    '(mouseleave)': 'onMouseLeave()',
    '[style.backgroundColor]': 'isHovered() ? highlightColor : "transparent"',
    '[style.cursor]': '"pointer"',
  },
})
export class HighlightDirectiveModern {
  highlightColor = input('yellow', { alias: 'appHighlight' });
  isHovered      = signal(false);
  onMouseEnter() { this.isHovered.set(true);  }
  onMouseLeave() { this.isHovered.set(false); }
}
Note: Template reference variables (#ref) on a plain HTML element reference the HTMLElement. On a component (<app-post-list #list />), the reference is the component class instance โ€” you can call its public methods and access its public properties. On a directive (<input ngModel #model="ngModel" />), the reference is the directive instance when the exported name matches. This allows templates to read directive state (model.valid, model.dirty) for local UI logic without going through the component class.
Tip: The host metadata property in @Component or @Directive is the modern equivalent of @HostBinding and @HostListener. Using host: { '(click)': 'onClick()', '[class.active]': 'isActive' } in the decorator metadata is more concise, avoids extra method decorators, and makes it clear at a glance what the component does to its host element. The Angular team recommends host metadata over decorators for new code.
Warning: Template reference variables are scoped to the template โ€” you cannot access #searchInput from the component class (that requires @ViewChild). Template references are for template-to-template communication: passing an element to an event handler or reading a property in interpolation within the same template. Do not confuse template references with @ViewChild โ€” they serve different purposes and scope.

Common Mistakes

Mistake 1 โ€” Accessing a template reference variable from the component class (undefined)

โŒ Wrong โ€” declaring #myInput in template and trying to use this.myInput in the class; it is a template-only reference.

โœ… Correct โ€” use @ViewChild('myInput') myInput!: ElementRef in the class to access template DOM elements.

Mistake 2 โ€” Using @HostListener for events that should be component events (wrong level)

โŒ Wrong โ€” @HostListener('click') inside a component when the template already has a click handler; fires twice.

โœ… Correct โ€” use @HostListener in directives that augment host elements; use template event bindings for component-owned elements.

🧠 Test Yourself

A template has <app-post-list #list /> and <button (click)="list.refresh()">. What is list in this context?