Simplifying ViewChild and ContentChild in Angular

Dhananjay Kumar / Tuesday, February 06, 2018

In this blog post, we will learn about ViewChild and ContentChild in Angular.

Essentially ViewChild and ContentChild are used for component communication in Angular. Therefore, if a parent component wants access of child component then it uses ViewChild or ContentChild.

Any component, directive, or element which is part of a template is ViewChild and any component or element which is projected in the template is ContentChild.

ViewChild and ViewChildren

If you want to access following inside the Parent Component, use @ViewChild decorator of Angular.

  1. Child Component
  2. Directive
  3. DOM Element

ViewChild returns the first element that matches the selector.

Let us assume that we have a component MessageComponent as shown in the below listing:

import { Component, Input } from '@angular/core';
@Component({
    selector: 'app-message',
    template: `<h2>{{message}}</h2>`
})
export class MessageComponent {
    @Input() message: string;
 
}

We are using MessageComponent inside AppComponent as shown in below listing:

import { Component, OnInit } from '@angular/core';
@Component({
    selector: 'app-root',
    template: `
  <div>
  <h1>Messages</h1>
  <app-message [message]='message'></app-message>
  </div>`
})
export class AppComponent implements OnInit {
    message: any;
    ngOnInit() {
        this.message = 'Hello World !';
    }
}

In application, you will get the output as below:

Here, MessageComponent has become child of AppComponent. Therefore, we can access it as a ViewChild. Definition of ViewChild is:

The Child Element which is located inside the component template”,

Here MessageComponent is located inside template of AppComponent, so it can be accessed as ViewChild.

export class AppComponent implements OnInit, AfterViewInit {
    message: any;
    @ViewChild(MessageComponent) messageViewChild: MessageComponent;
 
    ngAfterViewInit() {
        console.log(this.messageViewChild);
    }
 
    ngOnInit() {
        this.message = 'Hello World !';
    }
}

We need to do following tasks:

  • Import ViewChild and AfterViewInit from @angular/core
  • Implement AfterViewInit life cycle hook to component class
  • Create a variable with decorator @ViewChild
  • Access that inside ngAfterViewInit life cycle hook

In the output console you will find reference of MessageComponent, also if you can notice that __proto__ of MessageComponnet is set to Object. 

Now let us try to change value of MessageComponent property

ngAfterViewInit() {
    console.log(this.messageViewChild);
    this.messageViewChild.message = 'Passed as View Child';
}

Here we are changing the value of ViewChild property, you will notice that value has been changed and you are getting output as below:

However, in the console you will find an error: “Expression has changed after it was last checked

This error can be fixed two ways,

  1. By changing the ViewChild property in ngAfterContentInit life cycle hook
  2. Manually calling change detection using ChangeDetectorRef

To fix it in ngAfterContentInit life cycle hook you need to implement AfterContentInit interface

ngAfterContentInit() {
    this.messageViewChild.message = 'Passed as View Child';
}

Only problem with this approach is when you work with more than one ViewChild also known as ViewChildren. Reference of ViewChildren is not available in ngAfterContnetInit life cycle hook. In that case, to fix the above error, you will have to use a change detection mechanism.  To use the change detection mechanism:

  1. Import ChangeDetectorRef from @angular/core
  2. Inject it to the constructor of Component class
  3. Call detectChanges() method after ViewChild property is changed

You can use manual change detection like shown in below listing:

constructor(private cd: ChangeDetectorRef) {}
 
ngAfterViewInit() {
    console.log(this.messageViewChild);
    this.messageViewChild.message = 'Passed as View Child';
    this.cd.detectChanges();
}

Manually calling change detection will fix “Expression has changed after it was last checked,” error and it can be used with ViewChildren also.

To understand ViewChildren, let us consider AppComponent class created as shown in below listing:

import { Component, OnInit } from '@angular/core';
 
@Component({
    selector: 'app-root',
    template: `
  <div>
  <h1>Messages</h1>
  <app-message *ngFor="let f of messages" [message]='f'></app-message>
  </div>`
})
export class AppComponent implements OnInit {
    messages: any;
    ngOnInit() {
        this.messages = this.getMessage();
    }
    getMessage() {
        return [
            'Hello India',
            'Which team is winning Super Bowl? ',
            'Have you checked Ignite UI ?',
            'Take your broken heart and make it to the art'
        ];
    }
}

We are using MessageComponent inside a *ngFor directive hence there are multiple references of MessageComponent. We can access it now as ViewChildren and QueryList as shown in the listing below:

@ViewChildren(MessageComponent) messageViewChildren: QueryList<MessageComponent>;
ngAfterViewInit() {
    console.log(this.messageViewChildren);
}

To work with ViewChildren and QueryList, you need to do following tasks:

  • Import ViewChildren , QueryList , AfterViewInit from @angular/core
  • Make reference of ViewChildren with type QueryList
  • Access ViewChildren reference in ngAfterViewInit() life cycle hook

In the output, you will get various reference of MessageComponent as ViewChildern as shown in the image below:

Now let us try to update properties of ViewChildren as shown in the listing below:

ngAfterViewInit() {
    console.log(this.messageViewChildren);
    this.messageViewChildren.forEach((item) => { item.message = 'Infragistics'; });
}

As you see, we are iterating through each item of ViewChildren and updating each property. This will update property value but again you will get the error, “Expression has changed after it was last checked” as shown in the image below:

You can again fix it by manually calling change detection like ViewChild. Keep in mind that we do not have ViewChildren reference available in AfterContentInit life cycle hook. You will get undefined in ngAfterContentInit() life cycle hook for ViewChildren reference as shown in the listing below :  

ngAfterContentInit() {
    console.log(this.messageViewChildren); // undefined 
}

However, you can manually call change detection to fix error:  “Expression has changed after it was last checked”

 To use a change detection mechanism

  1. Import ChangeDetectorRef from @angular/core
  2. Inject it to the constructor of Component class
  3. Call detectChanges() method after ViewChild property is changed

You can use a manual change detection like shown in below listing:

@ViewChildren(MessageComponent) messageViewChildren: QueryList<MessageComponent>;
constructor(private cd: ChangeDetectorRef) {
}
ngAfterViewInit() {
    console.log(this.messageViewChildren);
    this.messageViewChildren.forEach((item) => { item.message = 'Infragistics'; });
    this.cd.detectChanges();
}

In this way, you can work with ViewChild and ViewChildren.

ContentChild and ContnetChildren

Let us start with understanding about ContnetChild. Any element which is located inside the template, is ContnetChild.

To understand it let us consider MessageContainerComponent.

import { Component } from '@angular/core';
@Component({
    selector: 'app-messagecontainer',
    template: `
    <div>
    <h3>{{greetMessage}}</h3>
    <ng-content select="app-message"></ng-content>
    </div>
    `
})
export class MessageContainerComponent {
    greetMessage = 'Ignite UI Rocks!';
}

In this component, we are using Angular Content Projection.  You can learn more about content projection here

Any element or component projected inside <ng-content> becomes a ContentChild. If you want to access and communicate with MessageComponent projected inside MessageContainerComponent, you need to read it as ContnetChild.

Before we go ahead and learn to use ContentChild, first see how MessageContainerComponent is used and MessageComponent is projected,

import { Component, OnInit } from '@angular/core';
@Component({
    selector: 'app-root',
    template: `
  <div>
  <app-messagecontainer>
  <app-message [message]='message'></app-message>
  </app-messagecontainer>
  </div>`
})
export class AppComponent implements OnInit {
    message: any;
    ngOnInit() {
        this.message = 'Hello World !';
    }
}

As you see in the above listing that in the AppComponent, we are using MessageContainerComponent and passing MessageComponent to be projected inside it. Since MessageComponent is used in MessageContainerComponent using content projection, it becomes ContentChild.

Now, you will get output as shown below:

Since, MessageComponnet is projected and being used inside template of MessageContainerComponent, it can be used as ContentChild as shown in the below listing:

import { Component, ContentChild, AfterContentInit } from '@angular/core';
import { MessageComponent } from './message.component';
 
@Component({
    selector: 'app-messagecontainer',
    template: `
    <div>
    <h3>{{greetMessage}}</h3>
    <ng-content select="app-message"></ng-content>
    </div>
    `
})
export class MessageContainerComponent implements AfterContentInit {
    greetMessage = 'Ignite UI Rocks!';
    @ContentChild(MessageComponent) MessageComponnetContentChild: MessageComponent;
    ngAfterContentInit() {
        console.log(this.MessageComponnetContentChild);
    }
}

We need to do the following tasks:

  • Import ContnetChild and AfterContnetInit from @angular/core
  • Implement AfterContnetInit life cycle hook to component class
  • Create a variable with decorator @ContnetChild
  • Access that inside ngAfterContnetInit life cycle hook

 In the output console you will find a reference of MessageComponent, also if you can notice that __proto__ of MessageComponent is set to Object. 

 

You can modify the ContentChild property inside ngAfterContentInit life cycle hook of the component. Let us assume that there is more than one MessageComponent is projected as shown in the listing below:

import { Component, OnInit } from '@angular/core';
@Component({
    selector: 'app-root',
    template: `
  <div>
    <app-messagecontainer>
    <app-message *ngFor='let m of messages' [message]='m'></app-message>
    </app-messagecontainer>
  </div>`
})
export class AppComponent implements OnInit {
    messages: any;
    ngOnInit() {
        this.messages = this.getMessage();
    }
    getMessage() {
        return [
            'Hello India',
            'Which team is winning Super Bowl? ',
            'Have you checked Ignite UI ?',
            'Take your broken heart and make it to the art'
        ];
    }
}

In the output, you will get many MessgeComponent projected as below:

Now we have more than one ContentChild, so we need to access them as ContentChildren as shown in the listing below:

export class MessageContainerComponent implements AfterContentInit {
    greetMessage = 'Ignite UI Rocks!';
    @ContentChildren(MessageComponent) MessageComponnetContentChild: QueryList<MessageComponent>;
    ngAfterContentInit() {
        console.log(this.MessageComponnetContentChild);
    }
}

To work with ContentChildren and QueryList, you need to do following tasks:

  • Import ContentChildren , QueryList , AfterContentInit from @angular/core
  • Make reference of ContnetChildren with type QueryList
  • Access ContentChildren reference in ngAfterContentInit() life cycle hook

 In the output, you will get various reference of MessageComponent as ContentChildren as shown in the image below:

You can query each item in ContentChildren and modify property as shown in the listing below:

ngAfterContentInit() {
    this.MessageComponnetContentChild.forEach((m) => m.message = 'Foo');
}

In this way, you can work with ContentChildren in Angular.

Summary

ViewChild and ContentChild are two very important features of Angular. It is used to access Child Component in the Parent Component.

Any directive, component, and element which is part of component template is accessed as ViewChild. Whereas, any element or component which is projected inside <ng-content> is accessed as ContentChild.

Like this post?

If you like this post, please share it. In addition, if you haven’t checked out Infragistics Ignite UI for Angular Components, be sure to do so! They’ve got 30+ material based Angular components to help you code speedy web apps faster.