The companion to Angular's ViewChild
is ContentChild
. It performs a similar duty; it retrieves a reference to the target child component and makes it available as a member of the parent component instance. The difference is that ContentChild
retrieves the markup that exists inside the parent component's selector tags, whereas ViewChild
retrieves the markup that exists inside the parent component's view.
The difference is best demonstrated by a comparison of behavior, so this recipe will convert the example from Configuring Mutual Parent-Child Awareness with ViewChild and forwardRef to use ContentChild
instead.
The code, links, and a live example of this are available at .
Begin with the code from the Configuring mutual parent-child awareness with ViewChild and forwardRef recipe.
Before you begin the conversion, you'll need to nest the ArticleComponent
tags inside another root component, as ContentChild
will not work for the root-level bootstrapped application component. Create a wrapped RootComponent
:
[app/root.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'root', template: ` <article></article> ` }) export class RootComponent {}
ContentChild
is introduced to components in essentially the same way as ViewChild
. Inside ArticleComponent
, perform this conversion and replace the <feedback>
tag with <ng-content>
:
[app/article.component.ts] import {Component, ContentChild} from '@angular/core'; import {FeedbackComponent} from './feedback.component'; @Component({ selector: 'article', template: ` <input type="checkbox" (click)="changeLikesEnabled($event)"> <ng-content></ng-content> ` }) export class ArticleComponent { @ContentChild(FeedbackComponent) feedbackComponent:FeedbackComponent; likes:number = 0; incrementLikes():void { this.likes++; } changeLikesEnabled(e:Event):void { this.feedbackComponent.setLikeEnabled(e.target.checked); } }
Of course, this will only be able to find the child component if the <article></article>
tag has content inside of it:
[app/root.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'root', template: ` <article> <feedback></feedback> </article> ` }) export class RootComponent {}
You'll notice that the like count value being passed to the child component as an input has been removed. Very simply, that convention will not work anymore, as binding it here would draw the like count from RootComponent
, which does not have this information.
The FeedbackComponent
will need to retrieve the like count directly:
[app/feedback.component.ts] import {Component, Inject, forwardRef} from '@angular/core'; import {ArticleComponent} from './article.component'; @Component({ selector: 'feedback', template: ` <h1>Number of likes: {{ val }}</h1> <button (click)="likeArticle()" [disabled]="!likeEnabled"> Like this article! </button> ` }) export class FeedbackComponent { private val:number; private likeEnabled:boolean = false; private articleComponent:ArticleComponent; constructor(@Inject(forwardRef(() => ArticleComponent)) articleComponent:ArticleComponent) { this.articleComponent = articleComponent; this.updateLikes(); } updateLikes() { this.val = this.articleComponent.likes; } likeArticle():void { this.articleComponent.incrementLikes(); this.updateLikes(); } setLikeEnabled(newEnabledStatus:boolean):void { this.likeEnabled = newEnabledStatus; } }
That's it! The application should behave identically to the setup from the Getting ready section of the recipe.
ContentChild
does nearly the same thing as ViewChild
; it just looks in a different place. ContentChild
directs Angular to find the first instance of FeedbackComponent
present inside the ArticleComponent
tags. Here, this step refers to anything that is interpolated by <ng-content>
. It then assigns the found component instance to the decorated class member. The reference is updated along with any view updates. This decorated member will refer to the child component instance and can be interacted with like any normal object instance.
Since Angular performs hierarchical rendering, ContentChild
will not be ready until the view is initialized, but rather, after the AfterContentInit
life cycle hook. This can be demonstrated as follows:
[app/article.component.ts] import {Component, ContentChild, ngAfterContentInit} from '@angular/core'; import {FeedbackComponent} from './feedback.component'; @Component({ selector: 'article', template: ` <input type="checkbox" (click)="changeLikesEnabled($event)"> <ng-content></ng-content> ` }) export class ArticleComponent implements AfterContentInit { @ContentChild(FeedbackComponent) feedbackComponent:FeedbackComponent; likes:number = 0; constructor() { console.log(this.feedbackComponent); } ngAfterContentInit() { console.log(this.feedbackComponent); } incrementLikes():void { this.likes++; } changeLikesEnabled(e:Event):void { this.feedbackComponent.setLikeEnabled(e.target.checked); } }
This will first log undefined inside the constructor as the content, and therefore FeedbackComponent
does not yet exist. Once the AfterContentInit
life cycle hook occurs, you will be able to see FeedbackComponent
logged to the console.
If you would like to get a reference to multiple components, you can perform an identical reference acquisition process using ContentChildren
, which will provide you with QueryList
of all the matching components inside the component's tags.
A QueryList
can be used like an array with its toArray()
method. It also exposes a changes property, which emits an event every time a member of QueryList
changes.
ViewChild
to reference child component object instances.