Essential Parts of Angular’s Templating System — ng-content, ng-container, ng-template, ngTemplateOutlet

Sinan Öztürk
6 min readMar 6, 2024

--

  • These directives are pretty important for creating reusable,dynamic and customizable components in Angular applications.

ng-content

  • ng-content element allows you to add content from parent to the child component.
  • You can add single or multiple content.

Single-slot content projection

  • This is the simplest one. See;
  • In parent component, provide the content that you want to project to child, between child component element.
@Component({
imports: [ChildComponent],
selector: 'app-parent-component',
standalone: true,
template: `
<app-child-component>
<p>First rule of fight club is you do not talk about fight club.</p>
</app-child-component>
`,
})
export class ParentComponent{ }
  • Get the projected content in child component.
@Component({
selector: 'app-child-component',
standalone: true,
template: `
<div class="card">
<div class="card-title">Fight Clup</div>
<div class="card-body">
<ng-content></ng-content>
</div>
</div>
`,
})
export class ChildComponent { }

Multi-slot content projection

  • A component can have multiple slots. With this pattern, you must specify where you want the projected content to appear. You accomplish this task by using the select attribute of <ng-content>.
  • e.g, you might have a modal component which accepts content for body and footer sections.

Provide the name of content like a directive to the element:

<app-modal-component>
<p body>Beneath this mask there is an idea. And ideas are bulletproof.</p>
<p footer>V</p>
</app-modal-component>

Get the projected content:

<div class="card m-3 p-2 w-50">
<div class="card-title">V for Vandetta</div>
<div class="card-body">
<ng-content select="[body]"></ng-content>
</div>
<div class="card-footer">
<ng-content select="[footer]"></ng-content>
</div>
</div>

ng-container

  • We mostly use ng-container with structural directives because ng-container> allows us to use structural directives without any extra element.
  • Imagine you have fruits array and u want to print it to the screen. If you do this without ng-container it will look like this.
const fruits = [
{ name: 'Apple', color: 'red' },
{ name: 'Banana', color: 'yellow' },
{ name: 'Grapes', color: 'green' }
];

<ul>
<span *ngFor="let fruit of fruits">
<li>
{{ fruit.name }} is {{ fruit.color }}
</li>
</span>
</ul>
<ul>
<ng-container *ngFor="let fruit of fruits">
<li>
{{ fruit.name }} is {{ fruit.color }}
</li>
</ng-container>
</ul>

Another case for ng-container:

  • For example you want to use ngIf and ngFor to the same element.
<div *ngIf="fruits" *ngFor="let fruit of fruits">
<b>Name:</b>
<span>{{fruit}}</span>
</div>
  • If you try this, you will get an error: Can't have multiple template bindings on one element. Use only one attribute prefixed with *
  • Use ng-container as a wrapper for one of the structural directive.
<ng-container *ngIf="fruits">
<div *ngFor="let fruit of fruits">
<b>Name:</b>
<span>{{fruit}}</span>
</div>
</ng-container>
  • That’s not the only benefit of ng-container. In order to inject template dynamically we can use ng-container with ng-template and ngTemplateOutlet directives. We’ll come to that.

ng-template

  • <ng-template> cannot be used on its own. If we use <ng-template> as it is, nothing will shown on the page.
<p>Sinan</p>
<ng-template>github</ng-template> // This will not render anything to page
<p>997</p>
  • Because ng-template only defines a template. It is our job to tell angular where and when to display it.
  • <ng-template> elements are represented as instances of the TemplateRef class.

ng template with structural directives

  • Angular is already using ng-template behind the scenes with structural directives:ngIf, ngFor and ngSwitch. When you use structural directive, Angular interprets and converts into a longer form. Actually Angular transforms the * to <ng-template> and surround the content with ng-template behind the scenes.
  • See an example about it.

ng-template and ngIf directive

<div *ngIf="value" class="box">
Red Box
</div>

// above template is going to converted to this behind the scenes
<ng-template [ngIf]="value">
<div class="box">
Red Box
</div>
</ng-template>
  • The ng-template and ngIf directives in Angular are often used together to conditionally render content based on a condition. Here's how they work together:
<div *ngIf="showContent; else loading">
Hello World
</div>

<ng-template #loading>
Loading...
</ng-template>

There are few ways you can display the template.

  1. Using the TemplateRef & ViewContainerRef
  2. Using the ngTemplateOutlet directive.

TemplateRef & ViewContainerRef

  • Lets add ng-template to the DOM by using ViewContainerRef.
  • To instantiate embedded views based on a template, We use ViewContainerRef method createEmbeddedView(). See an example
import { Component, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
imports: [CommonModule],
selector: 'app-component',
standalone: true,
template: `
<button class="btn btn-primary" (click)="submit()">Add Template</button>
<ng-container #myContainer></ng-container>

<ng-template #myTemplate>
<p>Hello {{count}}</p>
</ng-template>
`,
})
export class AppComponent {
count = 0;
@ViewChild('myTemplate', {read: TemplateRef }) _template: TemplateRef<any>;
@ViewChild('myContainer', {read: ViewContainerRef}) vc: ViewContainerRef;

submit(){
this.count++;
this.vc.clear();
this.vc.createEmbeddedView(this._template);
}
}
  • Everytime you clicked the button it will clear the container and add template to the container.

NgTemplateOutlet

  • You can use ngTemplateOutlet in an ng-container element.
  • ngTemplateOutlet expects an templateRef and context. It’s a hard to explain in text, it is better to see an example 🙂

Star with a very simple example

import { Component } from '@angular/core';
import { CommonModule, NgTemplateOutlet } from '@angular/common';

@Component({
imports: [CommonModule, NgTemplateOutlet],
selector: 'app-parent-component',
standalone: true,
template: `
<ng-container *ngTemplateOutlet="myTemplate"></ng-container>

<ng-template #myTemplate>
<p>hello my reference will be used in ngTemplateOutlet</p>
</ng-template>
`,
})
export class AppComponent { }

Template Context

  • It’s like passing data to ngTemplateOutlet
<ng-container *ngTemplateOutlet="sayHiTemplate; context: { $implicit: 'Sinan' }"></ng-container>

<ng-template #sayHiTemplate let-name>
<p>Hello {{name}}</p>
</ng-template>
  • $implicit is the default key for context object.
  • Add an another property for context object
<ng-container 
*ngTemplateOutlet="sayHiTemplate; context: { $implicit: 'Sinan', surname: 'Öztürk' }">
</ng-container>

<ng-template #sayHiTemplate let-name let-surname='surname'>
<p>Hello {{name}} {{surname}}</p>
</ng-template>
  • You can convert *ngTemplateOutlet as follows.
<ng-container 
[ngTemplateOutlet]="sayHiTemplate"
[ngTemplateOutletContext]="{ $implicit: 'Sinan', surname: 'Öztürk' }"
>
</ng-container>

<ng-template #sayHiTemplate let-name let-surname='surname'>
<p>Hello {{name}} {{surname}}</p>
</ng-template>

Let’s create an modal component and use these directives

import { NgTemplateOutlet } from '@angular/common';
import { Component, Input, TemplateRef } from '@angular/core';

@Component({
selector: 'app-modal-component',
standalone: true,
imports: [NgTemplateOutlet],
styleUrl: './modal.component.scss',
template: `
<div class="my-modal">
<!-- header content -->
<ng-container *ngTemplateOutlet="headerTemplate || defaultHeader"></ng-container>

<!-- body content -->
<ng-content select="[body]"></ng-content>

<!-- footer content -->
<ng-container *ngTemplateOutlet="footerTemplate || defaultFooter"></ng-container>
</div>


<ng-template #defaultHeader>
<div class="header">
<button class="btn btn-secondary">
<i class="fa fa-xmark"></i>
</button>
</div>
</ng-template>

<ng-template #defaultFooter>
<div class="d-flex justify-content-end align-items-center">
<button class="btn btn-primary-outline">Cancel</button>
<button class="btn btn-primary">Save</button>
</div>
</ng-template>
`
})
export class ModalComponent {
@Input() headerTemplate: TemplateRef<any>;
@Input() footerTemplate: TemplateRef<any>;
}
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ModalComponent } from './modal.component';

@Component({
imports: [CommonModule, ModalComponent],
selector: 'app-parent-component',
standalone: true,
template: `
<app-modal-component [footerTemplate]="footerTemplate">
<div body>
<h1>Breaking Bad</h1>
<p>Story of a chemistry teacher in high school.</p>
</div>
</app-modal-component>


<ng-template #footerTemplate>
<div class="d-flex">
<span class="fw-bold me-1">Directed by:</span>
<span>Vince Gilligan</span>
</div>
</ng-template>
`,
})
export class ParentComponent { }

Result

I recommend you to checkout ui libraries source codes. These directives are used almost everywhere.

--

--