In Angular, we have different ways of communication between components. We can query and manipulate the child element/component using different decorators that Angular provides, like @ViewChild and @ContentChild depending on where that element might occur. We need to use the angular ContentChild/ContentChildren decorator to work with the elements inside the “ng-content” container.
The @ContentChild decorator tells Angular that the directive needs to query the host element’s content and assign the first result of the query to the property. The argument of @ContentChild decorator can be directive/component type or template variable name used for querying.query using template variable names, such that @ContentChild(“myVariable”) will find the first directive that has been assigned to myVariable.
An Angular official definition on ContentChild decorator as
“Parameter decorator that configures a content query. Use to get the first element or the directive matching the selector from the content DOM. If the content DOM changes and a new child matches the selector, the property will be updated.“
Difference between an Angular ContenChild/ViewChild decorator?
Any HTML element/component which is part of a template is ViewChild and any HTML element/component which is a project in a template is ContentChild. This means the angular ContentChild element is placed between the opening and closing tags of the host of a given component are called ContentChild.
The @ViewChild can access only HTML elements/components that are part of the view but not inside the content projection of the “ng-content” directive. We need to use @ContentChild and @ContentChildren decorator to work with the elements inside the “ng-content” directive.
The ContentChild() decorator searches the selector within content DOM elements which are projected from parent component to child component and returns the first element matching selector.
The @ContentChildren is a plural form of @ContentChild, the ContentChild is a parameter decorator that is used to fetch the QueryList of elements or directives from the content DOM. The QueryList is updated whenever the child element/component is added or removed.
Syntax of Angular ContentChild and angular ContentChildren decorator
@ContentChild(ChildComponent) firstChildItem: ChildComponent;
@ContentChildren(ListChildComponent) listItems: QueryList<ChildComponent>;
Return value of angular ContentChildren decorator
The return type of ContentChildren decorator is QueryList. The QueryList class is not a typical array, but it’s iterable in the sense we could use it in a loop as QueryList class implements the interface Iterable. QueryList also has an observable to which we can subscribe to it and get notified by Angular whenever the content changes. The QueryList is updated whenever the child element/component is added or removed.
Class QueryList<T> implements Iterable {
changes: Observable<any>;
.....
}
The QueryList is an unmodifiable list of items that Angular keeps up to date when the state of the application changes. The QueryList return object contains the following properties:
- first returns the first element matching the query
- last returns the last element matching the query
- length returns how many elements are matching the query
- changes return an observable that will emit the new QueryList every time an element matching the query is added, removed or moved
- result returns an array containing a list of all elements in Querylist
Example of @ContentChild and @ContentChildren decorator.
Let’s see an example that utilizes the power of @ContentChild and @ContentChildren decorator. We are creating a child component called CardComponent and performing the following task.
- The @ContentChild decorator to access the first matching of “cardBodyHeader” property decorator with @ContentChild in the content template. As soon as component content gets initialized, we are going to change the font color to red to the first matching card component body of the referenced item.
- The @ContentChildren decorator to print QureyList containing all matching of “cardBodyHeader” element of the content template.
Let’s first create a project to learn ContentChild/ContentChildren and add card components to it.
$ng new contentChildApp
$ng generate component card
Let’s update the app.component.ts file, we have two child card components in-app component template. For the first card component tag, we have three references of “cardBodyHeader” in its content and the second card component tag has one reference in the opening and closing of the card component tag in the parent template.
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div>
<h3>Parent Component header</h3>
<div class="child-component">
<app-card>
<h4 #cardBodyHeader>Card 1.1 body Header</h4>
<h4 #cardBodyHeader>Card 1.2 body Header</h4>
<h4 #cardBodyHeader>Card 1.3 body Header</h4>
<br/>
<span card-header >This should be rendered in header 1 selection of ng-content</span>
<div card-body >This should be rendered in body 1 selection of ng-content</div>
<span card-footer >This should be rendered in footer 1 selection of ng-content</span>
</app-card>
<app-card>
<h4 #cardBodyHeader>Card 2 body Header</h4>
<br/>
<span card-header >This should be rendered in header 2 selection of ng-content</span>
<div card-body >This should be rendered in body 2 selection of ng-content</div>
<span card-footer >This should be rendered in footer 3 selection of ng-content</span>
</app-card>
</div>
<div>
Parent Component content ...
</div>
</div>`,
styleUrls: ['./app.component.scss']
})
export class AppComponent { }
Let’s now update the custom card component, add the following code in the card.component.ts file as
import { Component, ContentChild, ElementRef, AfterContentInit, ContentChildren } from '@angular/core';
@Component({
selector: 'app-card',
template: `
<div class="card">
<div class="header">
<ng-content select="[card-header]"></ng-content>
</div>
<div class="body">
<ng-content></ng-content>
<ng-content select="[card-body]"></ng-content>
</div>
<div class="footer">
<ng-content select="[card-footer]"></ng-content>
</div>
</div>`,
styleUrls: ['./card.component.scss']
})
export class CardComponent implements AfterContentInit {
constructor() { }
@ContentChild('cardBodyHeader') cardBodyHeader: ElementRef;
@ContentChildren('cardBodyHeader') cardBodyHeaders: ElementRef;
ngAfterContentInit() {
this.cardBodyHeader.nativeElement.setAttribute('style', 'color:red');
console.log('Card body heading in card component' + this.cardBodyHeader.nativeElement.textContent);
console.log('CardBodyHeaders of QueryList: ', this.cardBodyHeaders);
}
}
Our card component now needs to implement the “AfterContentInit” interface and have the corresponding “ngAfterContentInit” method implementation. That is the most recommended place to work with the elements provided using the @ContentChild decorator.
When to access Angular ContentChild and Angular ContentChildren reference. ?
Properties decorated with @ContentChild and @ContentChildren are sure to be set before the ngAfterContentInit event hook on the component is called. This implies such properties are null if accessed inside the constructor. The reference to the content decorator variable is assigned once the content has been initialized. We can only access it once the Content is initialized and rendered.
Related Post
- Angular ViewChildren decorator in details
- How to implement Angular ViewChild Decorator
- What is Angular the ElementRef ?