Content projection in Angular
Content projection allows you to insert and project content into a component’s template from outside the component. This is a powerful technique for creating reusable and flexible components.
In Angular, content projection is achieved using the <ng-content>
element.
Single Slot Projection:
In a single-slot projection, you have a single <ng-content>
element in your component's template. This means that all content passed into the component will be projected into the same location in the component's template. Here's a simple example:
Parent component:
<div class="courses" *ngIf="courses[0] as course">
<course-card (courseSelected)="onCourseSelected($event)" [course]="course">
<img
width="300"
alt="Angular Logo"
src="{{ course.iconUrl }}"
*ngIf="course?.iconUrl"
else
noImageFound
/>
<p #noImageFound>
{{ course?.description }}
</p>
<div class="course-description">
{{ course?.longDescription }}
</div>
</course-card>
</div>
Child component:
<div class="course-card" [ngClass]="cardClasses()" *ngIf="showCard">
<div class="course-title" *ngIf="course?.description">
{{ index + ". " + course?.description }}
</div>
<ng-content></ng-content>
<button (click)="onCourseViewed(course)">View course</button>
</div>
So here in the above example, The child component tag (course-card
) in the parent, contains some content within its template, including an image (<img>
), a paragraph (<p>
), and a <div>
. All this content is intended to be projected into the child component.
Multiple Slot Projection:
In multi-slot projection, you can have multiple slots with different names. This allows you to project content into specific locations within the component’s template based on the slot name. Here’s an example:
Parent Component:
<div class="courses" *ngIf="courses[0] as course">
<course-card (courseSelected)="onCourseSelected($event)" [course]="course">
<img select="[img]"
width="300"
alt="Angular Logo"
src="{{ course.iconUrl }}"
*ngIf="course?.iconUrl"
else
noImageFound
/>
<p #noImageFound>
{{ course?.description }}
</p>
<div class="course-description" select="[description]">
{{ course?.longDescription }}
</div>
</course-card>
</div>
Child Component:
<div class="course-card" [ngClass]="cardClasses()" *ngIf="showCard">
<div class="course-title" *ngIf="course?.description">
{{ index + ". " + course?.description }}
</div>
<ng-content slot="img"></ng-content>
<ng-content slot="description"></ng-content>
<button (click)="onCourseViewed(course)">View course</button>
</div>
So here in the above example, In the parent component we use select
attribute to filter and select specific content for projection and in the parent we used Angular’s slot-based content projection with the slog
attribute along with the <ng-content>
.
We can also use selectors as well to project the content, like className, id and CSS selectors to find the the content to be projects. Below is a sample code:
Parent Component:
<div class="courses" *ngIf="courses[0] as course">
<ccourse-card (courseSelected)="onCourseSelected($event)" [course]="course">
<img select="[img]"
width="300"
alt="Angular Logo"
src="{{ course.iconUrl }}"
*ngIf="course?.iconUrl"
else
noImageFound
/>
<p #noImageFound>
{{ course?.description }}
</p>
<div class="course-description" select="[description]">
{{ course?.longDescription }}
</div>
<h5>Remaining content</h5>
</course-card>
</div>
Child Component:
<div class="course-card" [ngClass]="cardClasses()" *ngIf="showCard">
<div class="course-title" *ngIf="course?.description">
{{ index + ". " + course?.description }}
</div>
<ng-content select="img"></ng-content>
<ng-content select=".course-description"></ng-content>
<ng-content></ng-content>
<button (click)="onCourseViewed(course)">View course</button>
</div>
Here the parent component uses the course-card
component and tries to project content into different slots:
- The
img
tag is intended to be projected into the first<ng-content select="img">
slot. - The
div
with the classcourse-description
is intended to be projected into a named slot<ng-content select=".course-description">
- The
h5
tag with the text "Remaining content" is intended to be projected into the default slot which is<ng-content></ng-content>
Notes:
- The
select
attribute is typically used withng-content
to filter and select specific content based on the provided CSS selector. - Make sure that the CSS classes or selectors used in
select
match the structure of the content you want to project.
In summary, the ng-content
directive allows you to create a reusable child component that can accept and display content provided by its parent component. It provides a way to customize the appearance and behavior of the child component by projecting different content into it while maintaining a consistent structure.
How to Querying the template projected by content projection?
In Angular, @ViewChild
is used to get a reference to a child component or directive in the template. However, it does not work directly with projected content via ng-content
. This is because ng-content
is a placeholder for content provided by the parent component, and the actual rendering of the content happens outside the component's view.
When you use @ViewChild
, it looks for a component or directive in the current component's view, and since the content projected through ng-content
is not part of the current component's view, @ViewChild
cannot directly reference it.
If you need to interact with projected content in the child component, you can use a combination of @ContentChild
and ngAfterContentInit
lifecycle hook.
Here is link of @ContentChild
in details: https://gurindernarang.medium.com/accessing-child-component-instances-using-contentchild-and-contentchildren-4af6ebfe7b47