August 26, 2024
by
j1n ‐ 5
min. read
Read how to use content projection in Angular effectively in this article. From the basics to advanced techniques - practical examples to make your components flexible and maintainable.
Have you often wondered which structure makes the most sense for Angular components in your project? Or why the data flow becomes increasingly confusing over time? Perhaps you’re wondering whether an @Input()
is really necessary for a certain component - or what alternatives there are?
Then you should take a closer look at a useful tool in the Angular toolbox: the content projection with <ng-content/>
.
In the context of Angular, content projection describes a technique for inserting content from a parent component into a child component 1.
Compared to the @Input
decorator, Content Projection offers the advantage that a greater variety of elements can be transferred to the child component.
Possible contents:
To make sure we understand everything, let’s look at a practical example.
In the following app.component.ts
file, we have a parent component that uses the HeadlineComponent (child component).
@Component({
selector: 'app-root',
standalone: true,
imports: [HeadlineComponent],
template: '<app-headline>Example Headline</app-headline>',
styleUrl: './app.component.scss'
})
export class AppComponent {}
The content Example Headline
is projected into the <app-headline>
component using Content Projection.
@Component({
selector: 'app-headline',
standalone: true,
imports: [],
template: '<h1><ng-content></ng-content></h1>',
styleUrl: './headline.component.scss'
})
export class HeadlineComponent {}
The result in the DOM is an <h1>
tag, with Example Headline
as content.
<h1>Content Projection</h1>
If you want to look at the code yourself, you can find it in this repository, on the examples/basic branch.
Sometimes it is necessary to create more complex layouts that render different parts of content in different places. Angular offers the possibility to project multiple contents into one component by using multiple <ng-content>
placeholders.
The examples below can also be found at GitHub.
Component with multiple <ng-content>
elements:
<div class="card">
<h1>
<ng-content select="[card-title]"></ng-content>
</h1>
<ng-content select="card-body"></ng-content>
<ng-content select=".card-footer"></ng-content>
</div>
We recognize the familiar <ng-content>
.
Here, the select
attribute is used to project specific content into the corresponding placeholders. Example of use:
<app-custom-card>
<card-title>Hello</card-title>
<card-body>Welcome to the example</card-body>
</app-custom-card>
There are several ways to determine which <ng-content>
element is used.
Firstly, as in the example above, we can use <card-title>
and <card-body>
to reference the select
attributes of the respective <ng-content>
.
It is important to note that the parent component defines CUSTOM_ELEMENTS_SCHEMA
as schemas
. Otherwise Angular would search for an element with the selector <card-title>
. We therefore tell Angular that these are valid elements.
@Component({
...
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
<ng-content select="[card-title]"></ng-content>
<div card-title>
Card Title
</div>
<ng-content select=".card-title"></ng-content>
<div class="card-title">
Card Title
</div>
Up to this point, we have covered the basics of content projection in Angular and used examples to show how content can be projected from a parent component into a child component. We have explained the simple use of <ng-content>
and seen how to define multiple placeholders in a component to specifically insert different content.
In the following, we will focus on when it makes sense to use this technique.
<ng-content>
, the created component can accommodate a wide variety of content (see card example).@Input()
and @Output()
. This makes your code clearer, keeps data streams short and is easier to maintain for you and your team.<ng-content>
.In addition to the basic use cases, there are some additional aspects of <ng-content>
that you should know.
<ng-content>
should not be conditionally integrated with @If
etc., as Angular also creates the DOM elements for the non-visible placeholders 1.
It is possible to set a default value for <ng-content>
. This is placed between the opening and closing ng-content-tag, as in the example below.
The default is displayed if there is no content that corresponds to the specified select
of an <ng-content>
.
<div class="card">
<h1>
<ng-content select="[card-title]">DEFAULT-Value</ng-content>
</h1>
<ng-content select="card-body"></ng-content>
<ng-content select=".card-footer"></ng-content>
</div>
Furthermore it is also possible to use a fallback <ng-content>
. If we look at the card example again, we can see that there are several ng-content blocks with a select
. If the parent component tries to project content that does not correspond to one of the select
, it will not be displayed.
This problem can be solved by simply adding <ng-content/>
. The projected content that cannot be assigned to a select
is projected into this fallback <ng-content></ng-content>
.
With the help of ngProjectAs
we can specify a CSS selector. Whenever an element with ngProjectAs
is checked against a <ng-content>
placeholder, Angular compares with the ngProjectAs
value instead of the identity of the element. Here is another example:
<app-custom-card>
<div card-title>
Title
</div>
<h3 ngProjectAs="card-body">Hello</h3>
<div class="card-footer">Footer</div>
</app-custom-card>
In this case, the card-body
will be rendered as an h3
HTML element 1.