Content projection allows us to inject content anywhere else in our template. Content projection is useful when building reusable components. For example, instead of duplicating the same component in order to change only a part of the data, we might decide to use content projection.
We can project/inject content using the <ng-content></ng-content>
tag in the HTML of our components. The content we put inside our component tag is the content that is projected into the component.
Additionally, we can create multiple projection spaces using multi-slot projection by designating certain sections to an alias, by specifying a select
value.
Single-slot content projection
We mentioned above, that we can simply project content using a single data point. We can project as much content as we want, but we have only one insertion point.
How all of this works we can inspect best by creating a simple component that we will experiment on. Below you can find the template of the User Card component. (The full application can be found on the end inside the Stackblitz IDE). user-card.component.html
:
<div class="wrapper"> <div class="basic-info"> <div class="image"> <img src="https://avatars.dicebear.com/api/male/tom.svg?background=%230000ff" /> </div> <div class="personal-data"> <h3>Name:</h3> <p>Tom</p> <h3>Surname:</h3> <p>Doe</p> </div> </div> <div class="more-data"> <h3>Likes:</h3> <p>Football, swimming, beer</p> </div> </div>
We created a simple Card component that just displays some information about an imaginary person. For the colorful avatar, we used the super cool Dicebear API. We can then import this template into our app.component.html
by writing:
<app-user-card></app-user-card>
And we will get the following result:
Let’s say we want to display some more data about our person. We could simply add more data into the template, we could also use the @Input()
decorator to add more properties. But in this case, we will use content projection.
The simplest way to project data is to use <ng-content></ng-content>
tags inside the template and then inject data into the component selector. This will be our updated user-card.component.html
. Notice the new element at the end there – the <ng-content>
tags.
<div class="wrapper"> <div class="basic-info"> <div class="image"> <img src="https://avatars.dicebear.com/api/male/tom.svg?background=%230000ff" /> </div> <div class="personal-data"> <h3>Name:</h3> <p>Tom</p> <h3>Surname:</h3> <p>Doe</p> </div> </div> <div class="more-data"> <h3>Likes:</h3> <p>Football, swimming, beer</p> </div> <ng-content></ng-content> </div>
Then in our app.component.html
we can project our data by writing this:
<app-user-card> <div class="more-data"> <h3>Favorite day:</h3> <p>Sunday</p> </div> </app-user-card>
Great! Now we have our projected content. But… Something is wrong! Even though we managed to inject our data, it seems like our styling is all messed up!
How to fix this? The content in <ng-content>
is insulated from the component, it can’t see the component’s attribute or styling. To fix this, we will use the ::ng-deep
modifier, which will go beyond the component to apply stylings. We can imagine the CSS ‘leaking’ through to other components. Our user-card.component.css
will then be:
::ng-deep p { margin-top: 0; margin-bottom: 5px; color: #ecd9b5; } ::ng-deep h3 { margin-top: 0; margin-bottom: 5px; color: #ecd9b5; }
Multi-slot data projection
Now, let us designate multiple sections for data projection. We do that by specifying the select
property on ng-content
, and not only we can inject multiple data, but we can also tell the data where to appear! Notice the two ng-content
tags with different select
values, in the user-card.component.html
template below, one put above our ‘Likes’ attribute, and the other below:
<div class="wrapper"> <div class="basic-info"> <div class="image"> <img src="https://avatars.dicebear.com/api/male/tom.svg?background=%230000ff" /> </div> <div class="personal-data"> <h3>Name:</h3> <p>Tom</p> <h3>Surname:</h3> <p>Doe</p> </div> </div> <ng-content select="[fav-day]"></ng-content> <div class="more-data"> <h3>Likes:</h3> <p>Football, swimming, beer</p> </div> <ng-content select="[dislikes]"></ng-content> </div>
In order to inject data into both projection slots we just need to assign selectors to our projected elements. Selectors can be tag names, CSS classes and the :not
pseudo-classes. Our app.component.html
will look like this now:
<app-user-card> <div class="more-data" fav-day> <h3>Favorite day:</h3> <p>Sunday</p> </div> <div class="more-data" dislikes> <h3>Dislikes:</h3> <p>The alarm clock</p> </div> </app-user-card>
For the complete project, please look at the Stackblitz below:
Final Words
Hopefully, you liked the first of many Angular articles that we will have on this blog. Content projection has some great use cases. We could use it to create a wrapper component around some of our data, templates that can host any kind of different data, or simply just to inject different data in specific slots. For more information about content projection please check the official Angular documentation.
For more articles please click below, or check the blog.
Pingback: Angular ContentChild -
Comments are closed.