Depending on your application's separation of concerns, it might make sense for a child component in your application to reference a parent, and at the same time, for the parent to reference the child. There are two similar implementations that allow you to accomplish this: using ViewChild
and ContentChild
. This recipe will discuss them both.
The code, links, and a live example of this are available at .
Begin with the recipe setup shown in Referencing a parent component from a child component. Your objective is to add the ability to enable and disable the like button from the parent component.
The initial setup only gives the child access to the parent, which is only half of what you need. The other half is to give the parent access to the child.
Getting a reference to FeedbackComponent
that you see in the ArticleComponent
template view can be done in two ways, and the first way demonstrated here will use ViewChild
.
Using ViewChild
will allow you to extract a component reference from inside the view. More plainly, in this example, using ViewChild
will give you the ability to reference the FeedbackComponent
instance from inside the ArticleComponent
code.
First, configure ArticleComponent
so that it will retrieve the component reference:
[app/article.component.ts] import {Component, ViewChild} from '@angular/core'; import {FeedbackComponent} from './feedback.component'; @Component({ selector: 'article', template: ` <input type="checkbox" (click)="changeLikesEnabled($event)"> <feedback [val]="likes"></feedback> ` }) export class ArticleComponent { @ViewChild(FeedbackComponent) feedbackComponent:FeedbackComponent; likes:number = 0; incrementLikes():void { this.likes++; } changeLikesEnabled(e:Event):void { this.feedbackComponent.setLikeEnabled(e.target.checked); } }
The main theme in this new code is that the ViewChild
decorator implicitly understands that it should target the view of this component, find the instance of FeedbackComponent
that is being rendered there, and assign it to the feedbackCompnent
member of the ArticleComponent
instance.
At this point, you should be seeing your application throwing new errors, most likely about being unable to resolve the parameters for FeedbackComponent
. This occurs because you have set up a cyclic dependency: FeedbackComponent
depends on ArticleComponent
and ArticleComponent
depends on FeedbackComponent
. Thankfully, this problem exists in the domain of Angular dependency injection, so you don't really need the module, just a token that represents it. For this purpose, Angular 2 provides you with forwardRef
, which allows you to use a module dependency inside your class definition before it is defined. Use it as follows:
[app/feedback.component.ts] import {Component, Input, Inject, forwardRef} from '@angular/core'; import {ArticleComponent} from './article.component'; @Component({ selector: 'feedback', template: ` <h1>Number of likes: {{ val }}</h1> <button (click)="likeArticle()"> Like this article! </button> ` }) export class FeedbackComponent { @Input() val:number; private articleComponent:ArticleComponent; constructor(@Inject(forwardRef(() => ArticleComponent)) articleComponent:ArticleComponent) { this.articleComponent = articleComponent; } likeArticle():void { this.articleComponent.incrementLikes(); } }
With the cycle problem resolved, add the setLikeEnabled()
method that the parent component is invoking:
[app/feedback.component.ts] import {Component, Input, 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 { @Input() val:number; private likeEnabled:boolean = false; private articleComponent:ArticleComponent; constructor(@Inject(forwardRef(() => ArticleComponent)) articleComponent:ArticleComponent) { this.articleComponent = articleComponent; } likeArticle():void { this.articleComponent.incrementLikes(); } setLikeEnabled(newEnabledStatus:boolean):void { this.likeEnabled = newEnabledStatus; } }
With this, toggling the checkbox should enable and disable the like button.
ViewChild
directs Angular to find the first instance of FeedbackComponent
present inside the ArticleComponent
view and assign it to the decorated class member. The reference will be 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.
It's important to remember the duality of the component instance and its representation in the template. For example, FeedbackComponent
is represented by a feedback tag (pre-render) and a header tag and a button (post-render), but neither of these form the actual component. The FeedbackComponent
instance is a JavaScript object that lives in the memory, and this is the object you want access to. If you just wanted a reference to the template elements, this could be accomplished by a template variable, for example.
Since Angular performs hierarchical rendering, ViewChild
will not be ready until the view is initialized, but rather, after the AfterViewInit
life cycle hook. This can be demonstrated as follows:
[app/article.component.ts] import {Component, ViewChild, ngAfterViewInit} from '@angular/core'; import {FeedbackComponent} from './feedback.component'; @Component({ selector: 'article', template: ` <input type="checkbox" (click)="changeLikesEnabled($event)"> <feedback [val]="likes"></feedback> ` }) export class ArticleComponent implements AfterViewInit { @ViewChild(FeedbackComponent) feedbackComponent:FeedbackComponent; likes:number = 0; constructor() { console.log(this.feedbackComponent); } ngAfterViewInit() { 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 view, and therefore, FeedbackComponent
does not yet exist. Once the AfterViewInit
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 using ViewChildren
, which will provide you with a QueryList
of all the matching components in the view.
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.
ContentChild
to reference child component object instances