Understanding Content Projection in Angular
Content projection is a core Angular feature that makes components more flexible and reusable. It allows you to pass HTML content from a parent component into a child component, making your UI more dynamic.
Let's start with a hands-on example. First, create a component named my-cards and add it to app.component.html using its selector. Here's what that looks like:
Now, let's put content projection to the test. In app.component.html, add the following code:
<app-my-cards>
<span>
This is some content in between the card selector!
</span>
</app-my-cards>
When you view this in the browser, you'll notice the text between the selector—This is some content in between the card selector!—isn't displayed. Let's see why.
The span element here is the content child—it's both the content and a child of the selector. Since Angular doesn't automatically render content children, we need a way to project them into the child component. This is where the ng-content placeholder comes in.
The ng-content directive acts as a hole in the child component's template where projected content gets inserted. Let's update MyCardsComponent's template to include it:
<ng-content></ng-content>
After adding ng-content, you'll see the content appears as expected:
So what's happening here? The span content is being projected into the ng-content placeholder, which then renders it in the template.
But what if you need multiple placeholders? For instance, you might have content for a card header, body, and footer all within the same card. That's where the select attribute comes into play—it's a powerful tool for targeting specific content.
The select attribute can match a class, id, attribute, or an element. Let's see it in action. First, update app.component.html with the following:
<app-my-cards>
<header>Card Header</header>
<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>
Then, in my-cards.component.html, the template for MyCardsComponent, add this:
<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>
Here's what the output looks like:
In this example:
1. The first ng-content selector targets an element—the header tag.
2. The second selector matches an id, specifically card-sub-header.
3. The third selector matches a class, card-body.
4. The fourth selector matches an attribute, title.
Now, imagine a scenario where you can't use a header tag but need a div instead, as shown below—and you also can't modify the card component. What do you do?
<app-my-cards>
<div >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>
That's where ngProjectAs saves the day!
With ngProjectAs, you can tell Angular to treat an element as if it matches a different selector. Add this code:
<app-my-cards>
<div 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>
Notice the first line: a div tag with ngProjectAs='header'. Angular now treats this div as a header element for projection purposes, so the output remains correct.
That wraps things up for this post—I hope you found it helpful and enjoyable.
If you liked it, please like, share, and comment.
Stay tuned for the next posts on ContentChild and ContentChildren.
I'll be tweeting more about Angular, JavaScript, TypeScript, and CSS tips.
See you there!
Cheers
Happy Coding
