Книга: Angular 2 Cookbook
Назад: Referencing a parent component from a child component
Дальше: Configuring mutual parent-child awareness with ContentChild and forwardRef

Configuring mutual parent-child awareness with ViewChild and forwardRef

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.

Note

The code, links, and a live example of this are available at .

Getting ready

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.

How to do it...

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.

Configuring a ViewChild reference

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.

Correcting the dependency cycle with forwardRef

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();     }   }   

Adding the disable behavior

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.

How it works...

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.

Note

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.

There's more...

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.

ViewChildren

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.

Tip

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.

See also

  • Utilizing component lifecycle hooks gives an example of how you can integrate with Angular 2's component rendering flow
  • Referencing a parent component from a child component describes how a component can gain a direct reference to its parent via injection
  • Configuring mutual parent-child awareness with ContentChild and forwardRef instructs you on how to properly use ContentChild to reference child component object instances
Назад: Referencing a parent component from a child component
Дальше: Configuring mutual parent-child awareness with ContentChild and forwardRef

thank you
Flame
cant read the code since it is all on a single line. Also this comments section is russian
Rakuneque
DATA COLLECTION AND ANALYSIS Two reviewers extracted data and assessed methodological quality independently lasix torsemide conversion Many others were in that space already