Skip to main content

Understanding the ViewChild and ViewChildren decorators in Angular 10

 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.

View queries and AfterViewInit lifecycle hook

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.

The ViewChild decorator

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;

Supported ViewChild selectors

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.

Using the read property

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;
}

Using the static property

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.

ViewChildren

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.

Conclusion

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. 

Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...