Understanding and implementing content projection using ng template in Angular
Jeff Mattson December 23, 2024
This article explores conditional rendering using a TemplateRef within a component, suitable for more dynamic scenarios. The next blog post will delve into the fundamentals of content projection in Angular by utilizing the ng-content directive.
Introduction
The ng-template directive is how we create these TemplateRef objects within our Angular templates. Let's explore how this works.
Imagine a 'template' as a blueprint for a piece of your user interface. In Angular, TemplateRef is like a special container for these templates. It allows us to:
- Create reusable chunks of UI: Define a template once and use it multiple times within our application.
- Dynamically control what's displayed: Decide when and how to render this template based on our application's logic.
- Pass data to the template: It provides specific information to the template to alter the way it looks.
This is an example template. We have only defined it here.
Simply defining this here will NOT render this template.
We can access this TemplateRef with the name myTemplate
The above snippet uses ng-template to create a reusable piece of UI. Let's call it #exampleTemplate so we can reference it later. This template could be thought of as a blueprint for some part of our application.
To render this template onto the screen, we could use ng-container combined with ngTemplateOutlet.
ng-container
is a helpful component that does not add any content to the pagengTemplateOutlet
is doing all the magic here. It is telling Angular to take our exampleTemplate and render it at this location in our component.
Understanding Template Context Data
In the above example, we have only shown how to render static content. But we can design our templates to accept context data. The context data can be injected at the time of rendering through a context parameter. It could be passed either implicitly or explicitly.
Implicit Context Passing
In this example, we're passing data to the template without explicitly naming it. We use let-name within the ng-template to give this unnamed data a temporary name, which we've chosen to be 'name.'.
This allows us to use the value of this data (which is 'Devstoc' in this example) within the template by simply referring to it as 'name.'
The $implicit keyword is a shorthand way to pass data where you do not have an actual name for it. This comes in handy whenever we have a situation that needs just one value to pass through to the template.
This version attempts to:
- Use simpler words: Swap "context data" and "implicit example" for simpler words to describe the above scenario.
- Explain purpose: Clearly state the need for let-name and $implicit and how these are shortcuts for passing data.
Improve flow by breaking up explanation into much smaller sentences.
import { Component } from '@angular/core'; @Component({ selector: 'app-implicit-context-example', template: `Hello, {{ name }}! ` }) export class ImplicitContextExampleComponent {}
Explicit Context Passing
We pass data to the template in this example with specific names: 'greetingText' and 'userName.'
We declare temporary variables in the ng-template called 'greeting' and 'name.' These variables are directly bound to the 'greetingText' and 'userName' values that we passed.
This enables us to use 'greeting' and 'name' within the template to access the pieces of data we need.
- Use more simple language: Replace technical terms such as "context data" and "keys in the context object" with simpler words.
- Emphasize the core idea: The let keyword links the data provided to the variables of the template.
Enhance readability with fewer, simpler sentences and more concise explanation.
import { Component } from '@angular/core'; @Component({ selector: 'app-explicit-context-example', template: `{{ greeting }}, {{ name }}! ` }) export class ExplicitContextExampleComponent {}
Example Usage
ng-template as an input
Let's build a simple Angular example that demonstrates how to display personalized user information within a modal window. We'll achieve this by using an ng-template and passing specific data to it.
In this example, we'll dynamically show the list of onboarding tasks that a particular user still needs to complete.
- Use more descriptive language: Replacing "context-specific user data" with "personalized user information" for better clarity.
- The objective: to indicate the actual purpose of this example, demonstrating user-specific onboarding tasks.
Improving readability means shorter and more direct sentences.
Parent Component (parent.component.ts):
import { Component } from '@angular/core'; interface Task { description: string; completed: boolean; } @Component({ selector: 'app-parent', template: `{{task.description}}
` }) export class ParentComponent { userTasks: Task[] = [ { description: 'Set 2FA for my gMail account', completed: false }, { description: 'Disable mobile banking', completed: false }, { description: 'Welcome email of Devstoc', completed: false } ]; tasksRemaining(): boolean { return this.userTasks.some(task => !task.completed); } }
In our example, managerParentComponent manages a list of tasks with description and whether they are completed or not. We use an ng-template to define how the tasks shall be rendered. Once there are tasks which are not completed, then systemParentComponent displays a modal window.
The modal window receives the list of tasks from the ParentComponent and displays them dynamically according to the completion status. This is how a parent component can pass a template, #userTasksTemplate, along with data-in this case, the list of tasks-to a child component, the modal.
"template reference" will be replaced with easier-to-understand descriptions. This explanation focuses on the main aspects of interaction between a parent and a child.
Modal Component (modal.component.ts):
import { Component, Input, TemplateRef } from '@angular/core'; @Component({ selector: 'app-modal', template: ``, styles: [` .modal { border: 1px solid #c0c0c0; border-radius: 7px; padding: .8rem; width: 400px; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #ffffff; box-shadow: 0 4px 8px rgba(0, 0, 0, .1); } `] }) export class ModalComponent { @Input() contentTemplate!: TemplateRef ; @Input() context!: any; }
The ModalComponent is fed two pieces of information by the ParentComponent. This sets up how the content in the modal should be. It is where the precise information to be shown in the modal will come from. For this example, this will be the user's remaining onboarding tasks.
The ModalComponent then dynamically shows the content using ngTemplateOutlet by referencing the provided template and data.
Replace technical terms such as "input parameters" and "context" with more relatable explanations. Highlight how the modal component is using the information that it received. Use briefer sentences and a more colloquial writing style.
Use Cases for Rendering Dynamic Templates
You can use TemplateRef to create and display parts of your user interface based on the current situation or the data available at that moment. This is incredibly useful when you need to change what's shown on the screen after the initial page load.
Now, you might wonder when to use TemplateRef instead of ≶ng-content>.
- Dynamically control what's displayed: Change the content based on user actions, data updates, or other conditions.
- Pass data to the template: Provide specific information to the template to customize its appearance.
Let's explore some real-world examples where TemplateRef is the better choice:
Replacing technical terms like "instantiate templates" with more understandable concepts. Highlighting the advantages of using TemplateRef dynamic control and data passing. Breaking down the information into a more digestible format.
Creating Flexible Data Grids
Use Case: Building a component that displays data in a table format is a great use case, but the content inside every cell can vary from simple text to more complex things, like forms or charts, depending on the kind of data being shown.
Using TemplateRef allows users to create custom templates for every cell, making the grid component incredibly flexible.
Increased Customizability: Users can precisely specify how each type of cell data should be displayed (for example, formatting as currency, displaying date, dropdown menus).
Increased Reusability: The grid component itself is generic and can be reused in lots of scenarios.
Using this with
Creating Dynamic Lists with Custom Item Styles
Use Case: You are building a component that renders lists of items. However, the style of each list item should be different depending on its type. For instance, in a task list, "meetings," "deadlines," and "reminders" should have different visual representations.
TemplateRef equips your list component with the capability to accept custom templates for each list item. This makes the component extremely versatile:
It can be reused widely by using the same list component for different types of data; each type is likely to have its display style. It can also very well support situations where one would have to display varying types of information or may be to use different layouts for displaying each item on a list.
In short, this provides flexibility compared to having a strict structure to handle every item on the list.
Tabs with On-Demand Content
Use Case: You know, imagine building a component that has lots of tabs. In each tab, there is one section of content. And some of those sections can be kind of heavy—maybe they contain a form, or they contain a chart, or they load in data from an external source. So you're suddenly loading all this content into your application.
You can now use TemplateRef for the content of each tab to decide when that content is really being created and displayed.
Lazy Loading: You can set the tab component so it loads and displays the content of the currently active tab only. This results in significant performance improvements when handling large or complex content.
Efficiency: Unlike
In this sense, only the content required for the user's future actions is loaded.
Creating Consistent Form Fields
Use Case: You want all forms in your application to look and feel the same. You want to standardize the layout for labels and error messages, as well as the visual style.
Accepting a template reference for the actual input field lets you write a reusable form field wrapper component.
Flexibility: The wrapper can be shared between different types of inputs, such as text boxes, dropdown menus, and date pickers, while appearing uniform.
Dynamic Adaptability: You can easily change the input type within the wrapper without altering the wrapper's structure.
Using
Customizable Tooltips and Modals
Use Case: Let's assume you have to show some tooltips or pop-up windows known as modals that point to relevant information depending on a particular situation. For instance, you may want a tooltip to show detailed information about a user when his/her name is hovered over a dashboard.
Using the TemplateRef to define your tooltips or modals has the following advantages:
Customization: It is easy to customize the look and feel of a tooltip or modal for specific situations.
Dynamic Content: The content can be loaded dynamically based on the current situation, like which user is being viewed.
It is very difficult to get this kind of flexibility when using