During my last series on testing Angular UI Elements, I dove into various Angular Material components and laid out how to easily test them in “real world” solutions. While writing the post on Datepicker, I used the @ViewChild docorator to inject an ng-template into the DOM on selection of a date or click of a button. I realized how much I don’t know about the @ViewChild decorator. As I like to do, I decided to do a deep dive, develop a few examples to get my fingers under the decorator. Also as I like to do, I wrote up my findings on solutions using @ViewChild, as much for me as anyone reading this.
Some definitions
Decorator – ‘@’ annotated expression, that evaluates to a function at runtime. It’s used to add features to, or modify, classes or class members. It is a Typescript feature. https://www.typescriptlang.org/docs/handbook/decorators.html
Did you know? Decorators are considered experimental in the current version of Typescript, currently 3.9. They are enabled through the experimentalDecorators option in the tsconfig.json file.
Directive – In Angular, a decorator for a class that adds custom behaviors to elements in the DOM. This could change the structure of the DOM by adding or removing elements, or changing the appearance of an element in the DOM. https://angular.io/api/core/Directive
Selector – Identifies the DOM element that the Directive attaches to. Depending on the directive, it can be an element name, a class with @Component or @Directive, an attribute such as a template reference variable, an attribute/value pair (eg. type=’text’), a provider defined in the child tree, a provider defined as a string token, a Template Ref.
What is @ViewChild? And what about @ViewChildren?
The Angular site defines the @ViewChild decorator as
Property decorator that configures a view query. The change detector looks for the first element or the directive matching the selector in the view DOM. If the view DOM changes, and a new child matches the selector, the property is updated.
https://angular.io/api/core/ViewChild
Great! But what does that really mean? Let’s start breaking this down. Here’s an example of an @ViewChild decoratorated property
@ViewChild('ourFirstInput', { read: ElementRef, static: false }) testInput: ElementRef;
A property in OOP is somewhere between a field and a method in a class. In this case, our property is testInput. We’ll use testInput to reference the element we find throughout our code. It can have a getter and setter, allowing us to put a bit more functionality in it. This way we have more control than a field over how it’s accessed.
We defined decorator above. And a view query is a component that looks into the view (the DOM) and tries to find specific selector(s) (defined above). For both of these, it would be our @ViewChild decorator. @ViewChildren is another view query available in Angular, as are @ContentChild and @ContentChildren.
So getting back to our short example above, our @ViewChild looks into the rendered DOM, finds the element with the template reference variable ‘ourFirstInput’ (a child element of the view!), and stores it in the property testInput to allow us to execute code on that element.
@ViewChild finds the first element on the page that matches the selector. What if we have multiple elements we want to work with? In that case we would use @ViewChildren. The setup looks similar but there are two differences. @ViewChildren returns a QueryList of the type of element, below we use ElementRef. A QueryList is an iterable. @ViewChildren also does not have a static property.
@ViewChildren(TemplateRef, { read: false }) ourTemplates: QueryList<ElementRef>[];
@ViewChild Metadata Properties
When we’re looking at our code, we have two metadata properties for @ViewChild, read and static.
{ read: false, static: false }
What do these do? The static property allows us to determine if our query runs prior to or after change detection. Setting this to false, as we have above and is the default, forces the query to run in ngAfterViewInit. Setting this to true makes the results of the query available in ngOnInit. So if we need to resolve things like bindings to have a successful query, we need static set to false. In @ViewChildren, static is always false.
The read property allows us to grab some other element type, from within our query. For example, if we had the following HTML, where we use the angryEyes directive on a ‘P’ element
<p angryEyes>They are Angry. Yes, very angry.</p>
and we use @ViewChild with read set to AngryEyesDirective
@ViewChild(AngryEyesDirective, { read: AngryEyesDirective, static: false }) angryEyesElement: AngryEyesDirective;
we return the directive that we are querying for.
If we change our read property to ElementRef, with all else being the same like this
@ViewChild(AngryEyesDirective, { read: ElementRef, static: false }) angryEyesElement: AngryEyesDirective;
We would now get the P element that is referencing the AngryEyesDirective
If you are looking to run public methods from within the found Directive, you want to set read to the class name of the directive, otherwise the code will not be accessible.
Some Code
We’ll look at this with a simple example. Here we have our @ViewChild directive looking for a template reference variable named ‘testDecorator’. When we find the element that matches that query, we store it as an ElementRef in the property testDecorator. As we’re looking for the ElementRef, and that’s what is returned, we can avoid adding the read property to the call.
@ViewChild('testdecorator', { static: false }) testDecorator: ElementRef;
When we look in our DOM, we find the element.
<div #testdecorator>Testing</div>
Great! So now what? After we have our element, we want to do some thing with it. In our example the element has a text node. In Javascript, nodes have node types. The three most used are element, text and comment. I know the DIV has a text node because I output the element and looked.
Since we have a text node, we can use renderer2 to perform a setValue() on the node. As you saw in the example above, our text node within the div is the firstChild. So we call the below to set the value of the firstChild, replacing the existing text with our new text.
ngAfterViewInit() { this.renderer.setValue( this.testDecorator.nativeElement.firstChild, 'This was changed by @ViewChild!' ); }
What do we need to implement @ViewChild?
One thing I want to point out, is that the examples above are also using Renderer2 to manipulate the DOM. Why, when we can easily use a nativeElement directly? There are really two reasons. In Angular’s documentation for ElementRef it cautions
Use this API as the last resort when direct access to DOM is needed. Use templating and data-binding provided by Angular instead. Alternatively you can take a look at
https://angular.io/api/core/ElementRefRenderer2
which provides API that can safely be used even when direct access to native elements is not supported. 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.
And also directly warns that
Permitting direct access to the DOM can make your application more vulnerable to XSS attacks. Carefully review any use of
https://angular.io/api/core/ElementRefElementRef
in your code.
Outside of deciding whether to use Renderer2 or templating/data binding, setting ourselves up to use @ViewChild is fairly easy. It’s part of Core so we do not need any special imports in our module.ts file. If we want to use Renderer2, as well as our ViewChildren and other ‘niceties’, we have to import the following into our component.ts file.
import { AfterViewInit, ChangeDetectorRef, Component, Directive, ElementRef, OnInit, QueryList, Renderer2, TemplateRef, ViewChild, ViewChildren } from '@angular/core';
And finally, depending on where you are making the view query result available, you will have to implement OnInit as well as AfterViewInit. AfterViewInit is the more likely situation, and goes along with our static: false metadata property.
export class AppComponent implements OnInit, AfterViewInit
Wrapup
The above has been a peek into the @ViewChild directive. It’s power lies in its use along with Renderer2 to manipulate the DOM safely. It’s important to get our hands dirty, write some code and try different options to really start to understand how functionality like this works. My suggestion is to grab the github repo related to this post, and try different options to really be able to see how it works.