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.