Zara Cooper Software developer and technical writer.

Understanding the ViewChild and ViewChildren decorators in Angular 10

5 min read 1554

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:

We made a custom demo for .
No really. Click here to check it out.

@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. If you’d like to see more about how to use them, check out these examples.

Experience your Angular apps exactly how a user does

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. https://logrocket.com/signup/

LogRocket is like a DVR for web 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 - .

Zara Cooper Software developer and technical writer.

Leave a Reply