Dead simple content projection on Angular

After reading a lot of documentation and articles about content projection in Angular, it seems like it’s a big deal to do it. And there is no place with all the information in one place. Just a lot of bloated articles about nothing.

So I won’t waste your time. Content projection in Angular is dead simple.

Case 1: pass HTML to a component with a ng-content

app.html:

<my-component> <div>X</div> </my-component>

my-component.html:

Here it goes: <ng-content></ng-content>

Let’s discuss the former example. X sign inside a div is passed inside a body to my-component. We want to display it in the component in a place we choose. We choose to display it after the text “Here it goes:”. <ng-content></ng-content> is replaced by <div>X</div>. Dead simple.

Case 2: pass two HTML to a component

app.html:

<my-component> <div first>X</div> <div second>Y</div> </my-component>

my-component.html:

Here goes first: <ng-content select=”[first]”></ng-content> Here goes second: <ng-content select=”[second]”></ng-content>

Let’s discuss this example. We pass X inside a div to a my-component. But what we do is we differentiate it by using a template reference variable (https://angular.io/guide/template-syntax#ref-vars): #first.

So when my-component is rendered <ng-content select=”[first]”></ng-content> is replaced by <div>X</div>. It is because we use a select feature of ng-content to choose what template we want to show. The same goes for showing Y in place of second ng-content. Dead simple!

Case 3: What if the component user won’t pass a template reference variable?

Let’s say we have a code from case 1. But the component user does this:

app.html:

<my-component></my-component>

He forgot to pass something for ng-content here. We want to detect this situation and display a message “Didn’t receive X”. Here it goes. We change my-component.html to this:

Here it goes:

<div #ref><ng-content></ng-content></div> <span *ngIf=”ref.nativeElement.childNodes.length == 0">Didn’t receive X</span>

Boom! We wrap ng-content with a div and mark it with a template reference variable of a “ref” name. Then, in span, we use plain old JavaScript DOM query to check if there is nothing inside. If there are no child nodes, it means nothing inside — boom! We show a message, “Didn’t receive X!

Case 4: How to pass an HTML (template) inside a for loop

So now you know almost everything you should know about content projection in Angular. Now let’s say we have an Angular Material table inside our my-component component that looks like this:

… <ng-container *ngFor=”let column of displayedColumns” matColumnDef=”{{column.id}}”> <th mat-header-cell *matHeaderCellDef mat-sort-header>{{column.name}}</th> <td mat-cell *matCellDef=”let row”> {{row[column.id]}} </td> </ng-container> …

And displayedColumns is a @Input(). You pass displayedColumns to it that are defined above. And it contains these data:

displayedColumns: [] = [ {id: ‘id’, name: ‘Id’}, {id: ‘name’, name: ‘Name’}, {id: ‘progress’, name: ‘Progress’}, {id: ‘color’, name: ‘Color’}, ];

As you can see, you use {{row[column.id]}} to display a value inside a cell inside a table. The row contains the data. column.id is the id/name of the property inside a row that contains data. For example, for a row:

row = {color: ‘yellow’}

{{row[column.id]} for color column will return ‘yellow’

So long story short, you display a value from the dataSource inside a table. But what if you want the component user to customize how to display the value? Add a little bit of HTML to it?

Here it goes. Inside my-component you replace {{row[column.id]}} with this:

<ng-container *ngIf=”itemTemplate”> <ng-container *ngTemplateOutlet=”itemTemplate; context: {$implicit: {row: row, column: column, value: row[column.id]}}”> </ng-container> </ng-container> <ng-container *ngIf=”!itemTemplate”>{{row[column.id]}}</ng-container>

In my-component.ts you add a property itemTemplate:

@Input() itemTemplate: TemplateRef<any>;

And you pass template like this:

<my-component … [itemTemplate]=”itemTemplate”> <ng-template let-item #itemTemplate> column id: {{item.column.id}} value: {{item.value}} </ng-template> </my-component>

What? What happened here? Let’s break it down:

<ng-container *ngIf=”itemTemplate”>

It tells us to use itemTemplate only if it is provided.

<ng-container *ngTemplateOutlet=”itemTemplate; context: {$implicit: {row: row, column: column, value: row[column.id]}}”></ng-container>

This line tells to use itemTemplate, and provide an implicit content that will be an object containing: currently displayed row data, now displayed column, and a value to be displayed.

And what if a user won’t provide a template? We display plain old value:

<ng-container *ngIf=”!itemTemplate”>{{row[column.id]}}</ng-container>

Let us see how we use the component:

<my-component … [itemTemplate]=”itemTemplate”> <ng-template let-item #itemTemplate> column id: {{item.column.id}} value: {{item.value}} </ng-template> </my-component>

Basically, what we did here is we created a ng-template, and instruct to take the item as a context (the one we defined in my-component). So we can reference item.column, item.row and item.value. And we provide the template, that is, how the cell should be styled, what should be the HTML for it.

One thing to remember is that we also need to pass the template to the component “twice” because we need to give it with the parameter [itemTemplate]=”itemTemplate” to indicate template reference.

And that’s it!

Four most common use cases of content projection in Angular laid out in a dead-simple way. You just saved 8 hours; you’d waste reading spam articles and answers online!

If you like short and helpful articles like this one without babbling about stuff that does not matter, clap!

Join Summon The JSON waiting list, and receive 10 free programming wallpapers now!