Книга: Angular 2 Cookbook
Назад: Configuring mutual parent-child awareness with ViewChild and forwardRef
Дальше: 3. Building Template-Driven and Reactive Forms

Configuring mutual parent-child awareness with ContentChild and forwardRef

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.

Note

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

Getting ready

Begin with the code from the Configuring mutual parent-child awareness with ViewChild and forwardRef recipe.

How to do it...

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 {}   

Converting to ContentChild

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 {}   

Note

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.

Correcting data binding

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.

How it works...

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.

There's more...

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.

ContentChildren

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.

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 ViewChild and forwardRef instructs you on how to properly use ViewChild to reference child component object instances.
Назад: Configuring mutual parent-child awareness with ViewChild and forwardRef
Дальше: 3. Building Template-Driven and Reactive Forms

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