The @ViewChild
and @ViewChildren
decorators in Angular provide access to child elements in the view DOM by setting up view queries. A view query is a requested reference to a child element within a component view which contains metadata of the element. The scope of these decorators is limited to the component view and its embedded child views. These decorators are especially helpful in instances where being able to access and modify elements within the view in conventional ways is not possible.
For example, if a library ships with a component or directive with a public non-input or non-output property you’d like to change, these decorators would allow you to access and modify them. These decorators are also helpful in exposing providers configured in child components to inject dependencies ( like services, configuration values, etc. ) that the main component may not have access to.
In this article, we will cover how to use the @ViewChild
and @ViewChildren
decorators, what their properties do, and how to specify their properties.
The AfterViewInit
lifecycle hook is called when the component view and its child views are completely initialized. So for immediate modifications or assignments, the best place to access view queries would be in the ngAfterViewInit
callback because the view queries are already resolved and set. Trying to access them before ngAfterViewInit
responds may generate undefined values. However, the @ViewChild
decorator provides a static
property that can be set to resolve a view query before change detection runs. We’ll cover how to use this property below.
This decorator takes three properties, a selector
, a read
, and a static
property. The read
and static
properties are optional. These properties are specified like this:
@ViewChild(selector {read: readValue, static: staticValue}) property;
The selector
property specifies what child element within the component view is to be queried. According to the @ViewChild
documentation, five kinds of selectors are supported. These are:
1) Classes with @Component
or @Directive
decorators
In this first example, MenuItemComponent
is a queried from the MenuComponent
view:
@Component({ selector: 'menu-item', template: `<p>{{menuText}}</p>` }) export class MenuItemComponent { @Input() menuText: string; } @Component({ selector: 'menu', template: `<menu-item [menuText]="'Contact Us'"></menu-item>` }) export class MenuComponent{ @ViewChild(MenuItemComponent) menu: MenuItem; }
Here’s an example with a directive:
@Directive({ selector: '[textHighlight]' }) export class TextHighlightDirective{} @Component({ selector: 'profile', template: '<p textHighlight>Some text to highlight</p>' }) export class ProfileComponent{ @ViewChild(TextHighlightDirective) highlightedText: TextHighlightDirective; }
2) A template reference variable as a string. Template reference variables are commonly used within templates but in this instance, it is used to configure a view query:
@Component({ selector: 'menu-item', template: `<p>{{menuText}}</p>` }) export class MenuItemComponent { @Input() menuText: string; } @Component({ selector: 'menu', template: ` <menu-item #aboutUs [menuText]="'About Us'"></menu-item> <menu-item #contactUs [menuText]="'Contact Us'"></menu-item> ` }) export class MenuComponent{ @ViewChild('aboutUs') aboutItem: MenuItem; @ViewChild('contactUs') contactItem: MenuItem; }
3) A provider defined in the child component tree of the current component. In this example, the SampleService
is specified as a provider token for the FirstChildComponentClass
. Since <first-child>
is an element in the ParentComponent
we can access the SampleService
from it using the SampleService
class as a token:
export class SampleService {} @Component({ selector: 'first-child', providers: [SampleService] }) export class FirstChildComponent{} @Component({ selector: 'parent', template: '<first-child></first-child>' }) export class ParentComponent{ @ViewChild(SampleService) sampleService: SampleService; }
4) A provider defined through a string token. Although this is stated in the documentation, getting a provider through this method returns undefined values. This is a regression in Ivy which is enabled by default in Angular 9. A fix for this has been made but as of the publication of this article, it has not been included in any release. To get this to work, you’ll need to disable Ivy in the tsconfig.json
file and use ViewEngine
instead:
{ "angularCompilerOptions": { "enableIvy": false, } }
Here’s how you can use a provider defined through a string token as a selector:
@Component({ selector: 'first-child', providers: [{ provide: 'TokenA', useValue: 'ValueA' }] }) export class FirstChildComponent{} @Component({ selector: 'parent', template: '<first-child></first-child>' }) export class ParentComponent{ @ViewChild('TokenA') providerA: string; }
However, if you’d like to use this type of selector with Ivy, you can use the read
property to acquire a view query:
export class ParentComponent{ @ViewChild(FirstChildComponent, { read: 'TokenA' }) providerA: string; }
5) A TemplateRef
. It’s possible to access embedded templates using the @ViewChild
decorator, which can then be used to instantiate embedded views with ViewContainerRef
:
@Component({ selector: `container`, template: `<ng-template><h1>This container is empty</h1></ng-template>` }) export class ContainerComponent{ @ViewChild(TemplateRef) contTemplate: TemplateRef; }
For a better understanding of how to use these view queries once configured, check out these live examples for each of these kinds of selectors. They illustrate how you can use view queries to access and modify embedded views.
The read
property lets you select various tokens from the elements you query. These tokens could be provider tokens used for dependency injection or in some cases, be the type of view query. This is an optional property.
In the example below, the FirstChildComponent
has a provider configuration with all kinds of dependency tokens like a class, string tokens, and an injection token. These tokens in conjunction with the read
property can expose these dependencies to parent components that embed the FirstChildComponent
. All these dependencies have been accessed in the ParentComponent
using the ViewChild
decorator and the read
property specifying each of the corresponding tokens.
It’s also possible to specify the type the view query should be, using the read
property. In the same example, the fcElementRef
and fcComponent
properties are both queries of FirstChildComponent
but are of different types based on what the read
property was specified as:
export class SampleService {} export const ExampleServiceToken = new InjectionToken<string>('ExampleService'); @Component({ selector: 'first-child', providers: [ SampleService, { provide: 'TokenA', useValue: 'valueA' }, { provide: 'TokenB', useValue: 123 }, { provide: ExampleServiceToken, useExisting: SampleService }, { provide: 'TokenC', useValue: true } ] }) export class FirstChildComponent{} @Component({ selector: 'parent', template: `<first-child></first-child>` }) export class ParentComponent{ @ViewChild(FirstChildComponent, { read: 'TokenA' }) dependencyA: string; @ViewChild(FirstChildComponent, { read: 'TokenB' }) dependencyB: number; @ViewChild(FirstChildComponent, { read: 'TokenC' }) dependencyC: boolean; @ViewChild(FirstChildComponent, { read: SampleService }) sampleService: SampleService; @ViewChild(FirstChildComponent, { read: ElementRef }) fcElementRef: ElementRef; @ViewChild(FirstChildComponent, { read: FirstChildComponent }) fcComponent: FirstChildComponent; @ViewChild(FirstChildComponent, { read: ExampleServiceToken }) exampleService: SampleService; }
The static
property takes a boolean value and is optional. By default, it is false. If it is true, the view query is resolved before the complete competent view and data-bound properties are fully initialized. If set to false, the view query is resolved after the component view and data-bound properties are completely initialized.
In this example, the paragraph element is queried using both true
and false static
properties and the values logged for each in the ngOnInit
and ngAfterViewInit
callbacks:
@Component({ selector: 'display-name', template: '<p #displayName>{{name}}</p>' }) export class DisplayNameComponent implements OnInit, AfterViewInit{ @ViewChild('displayName', {static: true}) staticName: ElementRef; @ViewChild('displayName', {static: false}) nonStaticName: ElementRef; name: string = "Jane"; ngOnInit(){ logValues('OnInit'); } ngAfterViewInit(){ logValues('AfterViewInit'); } logValues(eventType: string){ console.log(`[${eventType}]\n staticName: ${this.staticName}, name value: "${this.staticName.nativeElement.innerHTML}"\n nonStaticName: ${this.nonStaticName}, name value: "${this.nonStaticName.nativeElement.innerHTML}"\n`); } }
This is what will be logged:
[OnInit] staticName: [object Object], name value: "" // static: true nonStaticName: undefined, name value: "" // static: false [AfterViewInit] staticName: [object Object], name value: "Jane" // static: true nonStaticName: [object Object], name value: "Jane" // static: false
In the ngOnInit
callback, none of the interpolated values have been initialized but with { static: true}
, the staticName
view query is already resolved but the nonStaticName
is undefined. However, after the AfterViewInit
event, all the view queries have been resolved.
You can view these live examples that better illustrate how to use the read
and static
properties with the @ViewChild
decorator.
The@ViewChildren
decorator works similarly to the @ViewChild
decorator but instead of configuring a single view query, it gets a query list. From the component view DOM, it retrieves a QueryList
of child elements. This list is updated when any changes are made to the child elements. The @ViewChildren
decorator takes two properties, a selector
and a read
property. These properties work in the same way as in the @ViewChild
decorator. Any child elements that match will be part of the list. Here’s an example:
@Component({ selector: 'item-label', template: `<h6>{{labelText}}</h6>` }) export class ItemLabelComponent{ @Input() labelText: string; } @Component({ selector: 'item', template: `<item-label *ngFor="let label of labels" [labelText]="label"></item-label>` }) export class ItemComponent{ @ViewChildren(ItemLabelComponent) allLabels: ItemLabelComponent; labels = ['recent', 'popular', 'new']; }
The length of allLabels
will be three as all the <item-label>
will be selected. The QueryList
has a number of methods you could use to manipulate the view queries.
For a more expansive illustration of how to use the @ViewChildren
decorator and specify the read
property, check out this example.
The @ViewChild
and @ViewChildren
decorators are excellent utilities for querying child elements in views. Understanding how to use them gives you more options for customizing component views. If you’d like to see more about how to use them, check out these examples.
Debugging Angular applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Angular state and actions for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your site including network requests, JavaScript errors, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket NgRx plugin logs Angular state and actions to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.
Modernize how you debug your Angular apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
One Reply to "Understanding the ViewChild and ViewChildren decorators in Angular 10"
In one word : Wow. This tutorial has
1) In-depth explanation of all the possibilities
2) Examples
I was writing notes as I read through the article. An explanation by a Pro