Exploring ContentChild and ContentChildren in Angular
In this guide, we will examine the ContentChild and ContentChildren decorators in Angular. These property decorators allow you to query and obtain references to projected content within a component.
Let's begin with a demonstration environment. We have a MyCardsComponent where the parent component (AppComponent) projects several items.
The parent Component Template Code:
<app-my-cards>
<div #header ngProjectAs='header'>Card Header</div>
<span id='card-sub-header'>Card Sub Header</span>
<div class="card-body">
This is a card Body!!!
</div>
<footer title="card-footer">
Card Footer.
</footer>
</app-my-cards>
The Child Component Code:
<ng-content select='header'></ng-content>
<ng-content select='#card-sub-header'></ng-content>
<ng-content select='.card-body'></ng-content>
<ng-content select='[title]'></ng-content>
The current output looks like this:
Our goal is to obtain a reference to the projected content within the child component to perform operations like adding styles. The ViewChild decorator won't work in this scenario, so we need to use ContentChild or ContentChildren.
Add the following code inside MyCardsComponent:
@ContentChild('header')
cardHeaderData: ElementRef = {
nativeElement: undefined
};
In this code, we define a property called cardHeaderData and decorate it with ContentChild. This property is of type ElementRef, which acts as a wrapper around a native element in a view.
One might wonder when we can first access this element and how to confirm we have it. Angular provides the ngAfterContentInit lifecycle hook for this purpose. This method fires once the projected content has been initialized.
Note
The projected content becomes accessible for the first time inside the ngAfterContentInit lifecycle hook.
Let's implement this hook to see it in action:
ngAfterContentInit() {
this.cardHeaderData
debugger;
}
When inspecting in the developer tools, we can observe the following:
Here we can see the reference to the projected element obtained through ContentChild, which is the native element. Once we have this reference, we can manipulate it by adding styles programmatically and performing other operations. To modify the style, add this code:
ngAfterContentInit() {
this.cardHeaderData.nativeElement.style.color = 'blue';
this.cardHeaderData.nativeElement.style.backgroundColor =
'yellow';
this.cardHeaderData.nativeElement.style.fontSize = '24px';
}
The resulting output is:
By accessing the property, we can target the nativeElement to change colors and apply various DOM manipulations.
So far, we've targeted HTML elements like a div. But what if we project a Component instead? Let's explore that scenario.
Create a component called ContentChildDemo. You can use the CLI to generate it, or follow this guide. Then use its selector in app.component.html as shown below:
<app-my-cards>
<app-content-child-demo></app-content-child-demo>
</app-my-cards>
Now, in my-cards.component.ts, add the following code:
<ng-content></ng-content>
The result is shown below:
Content projection is working correctly. Now let's create a property and decorate it with ContentChild:
@ContentChild(ContentChildDemoComponent)
contentChildDemoProperty: ContentChildDemoComponent | undefined;
Notice that the ContentChild decorator here takes the name of the component class to reference (ContentChildDemoComponent), whereas in the first example we passed a reference like header.
Key Points:
1️⃣ When accessing a component, pass the component class name directly.
2️⃣ Through the property referencing a projected component, you can invoke methods defined inside that component.
3️⃣ For native elements, add a template reference and pass that reference to ContentChild.
Let's understand when ContentChildren is needed. In the previous example, we projected only a single ContentChildDemoComponent. But what if we need to access multiple projected components?
Consider a scenario like this:
<app-my-cards>
<app-content-child-demo></app-content-child-demo>
<app-content-child-demo></app-content-child-demo>
<app-content-child-demo></app-content-child-demo>
</app-my-cards>
In this case, ContentChild will only return the first match. This is an important detail to remember. To retrieve all the projected components, we must use ContentChildren.
Let's add a new property:
@ContentChildren(ContentChildDemoComponent)
contentChildrenDemoProperty:
QueryList<ContentChildDemoComponent> | undefined;
And in the ngAfterContentInit method:
ngAfterContentInit() {
this.contentChildrenDemoProperty
debugger;
}
When we run and debug the application, we get the following result:
As shown above, ContentChildren returns a QueryList, which is an unmodifiable list. You can iterate through it to access each individual component and perform operations on all matching items.
That concludes our exploration. I hope you found this post informative and enjoyable.
If you liked this content, please show your support by liking ❤️ sharing 💞 and commenting 🧡.
Next up, we'll dive into ChangeDetection. Stay tuned for that update.
I'll be tweeting more content about Angular, JavaScript, TypeScript, and CSS tips and tricks.
Hope to connect with you there too!
Cheers 🍻
Happy Coding
