In this post you’re going to learn how to select an element in an Angular template.
The concept of getting a reference to an element is typically known as a “reference variable” (even in React they’re called “refs”).
Element references inside the template
First, let’s create an <input>
and give it an ngModel
directive:
<input ngModel>
Notice here how I’m not using the typical [(ngModel)]
syntax, as here we just want to allow Angular to set a local model and update on change. This allows us to then log out the model value in a different way momentarily!
To get a direct reference to the DOM element, we’ll introduce Angular’s ref syntax - the hash #
symbol. Adding it to our <input>
would allow us to do something interesting:
<input ngModel #username>
<p>{{ username.value }}</p>
We’ve now used #username
(you can call these template ref variables anything you like) and because it’s a reference to our <input ngModel>
, it’s actually re-binding the value under-the-hood each time we type.
As
#username
is now a reference variable bound to the Angular component, we can access it anywhere - which is exactly what I’m doing with{{ username.value }}
and logging out the value!
Start typing in the live Stackblitz demo to see the model reflect as we type, note we’re using the username
reference exports the reference to the DOM Node:
Element references inside a Component class
So we know how to access our #username
inside the template, but what about the component?
For this, we need to introduce ViewChild, which can be used as a decorator on a property:
@Component({...})
export class AppComponent {
@ViewChild('username') input;
}
Now anywhere inside the class we can reference this.input
and get the element ref! But, what type is this?
Free eBook
Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.
- Observables and Async Pipe
- Identity Checking and Performance
- Web Components <ng-template> syntax
- <ng-container> and Observable Composition
- Advanced Rendering Patterns
- Setters and Getters for Styles and Class Bindings
A ViewChild
can return a few different things, one being an ElementRef - an element reference! This looks like:
class ElementRef<T> {
constructor(nativeElement: T)
nativeElement: T
}
This <T>
is a generic in TypeScript, which means we should supply further type information for it to infer down to the constructor
and also the nativeElement
property. Here’s how we can do that:
@Component({...})
export class AppComponent {
@ViewChild('username') input: ElementRef<HTMLInputElement>;
}
So surely now we can just go ahead and access this.input
?
@Component({...})
export class AppComponent {
@ViewChild('username') input: ElementRef<HTMLInputElement>;
constructor() {
// undefined
console.log(this.input);
}
}
Inside the constructor
our this.input
reports as undefined
, and moving this console.log
to ngOnInit
solves the issue as the component template is ready then:
So surely now we can just go ahead and access this.input
?
@Component({...})
export class AppComponent implements OnInit {
@ViewChild('username') input: ElementRef<HTMLInputElement>;
ngOnInit() {
// ElementRef { nativeElement: <input> }
console.log(this.input);
}
}
We can totally use ngOnInit
, but there’s actually a lifecycle hook built exclusively for when our ViewChild
will be ready - and that’s the AfterViewInit
hook. Let’s refactor:
@Component({...})
export class AppComponent implements AfterViewInit {
@ViewChild('username') input: ElementRef<HTMLInputElement>;
ngAfterViewInit() {
// ElementRef { nativeElement: <input> }
console.log(this.input);
}
}
So to use the native <input>
we’ll need to reference this.input.nativeElement
each time.
👏 It’s advised not to use properties on
this.input.nativeElement
directly however - use the Renderer2 class!
The docs for ElementRef suggest “Relying on direct DOM access creates tight coupling between your application and rendering layers which will make it impossible to separate the two and deploy your application into a web worker.”
I agree, so let’s do it properly.
The Renderer2 is considered the preferred option. Putting everything together, our final solution ends up as follows:
import { Component, ViewChild, ElementRef, AfterViewInit, Renderer2 } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<div>
<input ngModel #username>
<p>{{ username.value }}</p>
</div>
`
})
export class AppComponent implements AfterViewInit {
@ViewChild('username') input: ElementRef<HTMLInputElement>;
constructor(private renderer: Renderer2) {}
ngAfterViewInit() {
// ElementRef: { nativeElement: <input> }
console.log(this.input);
// Access the input object or DOM node
console.dir(this.input.nativeElement);
// Manipulate via Renderer2
this.renderer.setStyle(this.input.nativeElement, 'background', '#d515a0');
}
}
I’ve included a Stackblitz embed again to demonstrate the full setup with Renderer2
and ElementRef
:
Summary
Lots of things to think about here when we want to dive into the DOM alongside Angular, just ensure if you need to touch the native DOM elements that you use the right approach via the Renderer2
class.
The most powerful methods come from referencing the DOM element inside the class, but we started out learning about template reference variables and moved onto using class methods and the AfterViewInit
lifecycle hook.
We’ve covered how to use ViewChild
and how it can return us an ElementRef
, and we’ve explored the .nativeElement
property on the @ViewChild('username') input
property and learned how to use a generic type.
If you are serious about your Angular skills, your next step is to take a look at my Angular courses where you’ll learn Angular, TypeScript, RxJS and state management principles from beginning to expert level.
Happy coding!