Using ng-template for Dynamic UI in Angular: A Comprehensive Guide to Flexible Rendering
The ng-template directive in Angular is a powerful tool for creating dynamic and flexible user interfaces by defining reusable template fragments that can be rendered conditionally or repeatedly based on runtime logic. Unlike regular HTML, ng-template content is not rendered by default, making it ideal for scenarios where you need to control when and where content appears, such as in custom directives, dynamic lists, or modal dialogs. This in-depth guide explores how to use ng-template for dynamic UI in Angular, covering its syntax, integration with structural directives, and advanced techniques like template context and TemplateRef. Through a practical example of a task management application, you’ll learn to leverage ng-template to build versatile, maintainable, and dynamic Angular UIs.
What is ng-template?
The ng-template directive is a special Angular element that defines a block of HTML content that is not rendered immediately. Instead, it serves as a template that can be instantiated programmatically or used with structural directives like ngIf, ngFor, or custom directives. By pairing ng-template with TemplateRef and ViewContainerRef, developers can control its rendering dynamically, enabling complex UI patterns.
For example:
This content is not rendered until used.
The content inside remains dormant until referenced by a directive or component.
Why Use ng-template for Dynamic UI?
- Flexible Rendering: Render templates conditionally or repeatedly based on runtime conditions.
- Reusability: Define reusable template fragments that can be applied in multiple contexts.
- Dynamic Control: Programmatically instantiate templates using TemplateRef for custom rendering logic.
- Integration: Works seamlessly with Angular’s structural directives (ngIf, ngFor) and custom directives.
- Clean Templates: Keep complex UI logic separate from static HTML, improving readability and maintainability.
To understand Angular directives broadly, see Angular Directives.
Prerequisites
Before starting, ensure you have: 1. Node.js and npm: Version 16.x or later. Verify with:
node --version
npm --version
- Angular CLI: Install globally:
npm install -g @angular/cli
Check with ng version. See Mastering the Angular CLI. 3. Angular Project: Create one if needed:
ng new task-app
Select Yes for routing and CSS for styling. Navigate to cd task-app. Learn more in Angular Create a New Project. 4. Basic Knowledge: Familiarity with HTML, CSS, JavaScript, and TypeScript. Knowledge of Angular components, directives, and dependency injection is helpful. See Angular Component.
Step-by-Step Guide: Using ng-template for Dynamic UI
We’ll build a task management application that uses ng-template to create dynamic UI elements, including:
- A task list with conditional rendering of templates using *ngIf and else.
- A custom directive (appDynamicTemplate) to render ng-template content based on task priority.
- A component that programmatically renders ng-template for a modal dialog displaying task details.
Step 1: Set Up the Angular Project
Create a new project:
ng new task-app
cd task-app
- Select Yes for routing and CSS for styling.
Step 2: Install Bootstrap (Optional)
To enhance the UI, install Bootstrap:
npm install bootstrap
Update src/styles.css:
@import "~bootstrap/dist/css/bootstrap.min.css";
For detailed setup, see Angular Install and Use Bootstrap.
Step 3: Generate Components and Directives
Create components for the task list and a modal dialog:
ng generate component task-list
ng generate component task-modal
Create a custom directive to render templates dynamically:
ng generate directive dynamic-template
Step 4: Implement the Task List Component
The TaskListComponent will display tasks and use ng-template for conditional rendering.
Update task-list.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-task-list',
templateUrl: './task-list.component.html',
styleUrls: ['./task-list.component.css']
})
export class TaskListComponent {
tasks = [
{ id: 1, name: 'Learn Angular', priority: 'high', completed: false, details: 'Study ng-template and directives' },
{ id: 2, name: 'Build Task App', priority: 'medium', completed: true, details: 'Create a dynamic UI' },
{ id: 3, name: 'Deploy Project', priority: 'low', completed: false, details: 'Deploy to production' }
];
selectedTask: any = null;
toggleCompletion(taskId: number) {
const task = this.tasks.find(t => t.id === taskId);
if (task) {
task.completed = !task.completed;
}
}
selectTask(task: any) {
this.selectedTask = task;
}
clearSelection() {
this.selectedTask = null;
}
trackById(index: number, task: any) {
return task.id;
}
}
- Explanation:
- tasks: Array of tasks with id, name, priority, completed, and details.
- selectedTask: Tracks the task selected for the modal.
- toggleCompletion(): Toggles task completion.
- selectTask() and clearSelection(): Manage modal display.
- trackById(): Optimizes *ngFor. See [Use NgFor for List Rendering](/angular/directives/use-ngfor-for-list-rendering).
Update task-list.component.html:
Task List
{ { task.name }}
Priority: { { task.priority | titlecase }}
{ { task.completed ? 'Mark Incomplete' : 'Mark Complete' }}
View Details
{ { task.name }}: { { task.details }}
No tasks available
You have { { tasks.length }} task(s)
{ { selectedTask.name }}
Details: { { selectedTask.details }}
Priority: { { selectedTask.priority | titlecase }}
- ng-template Usage:
- #taskTemplate: Defines a template for task details, used later with a custom directive. The let-task provides context for the task data.
- #taskList: Used with *ngIf’s else clause to show the task count when tasks exist. See [Use NgIf in Templates](/angular/directives/use-ngif-in-templates).
- #modalContent: Passed to TaskModalComponent for custom modal content. See [Use Content Projection](/angular/components/use-content-projection).
- Other Features:
- ngFor**: Renders task cards in a Bootstrap grid. See [Use NgFor for List Rendering](/angular/directives/use-ngfor-for-list-rendering).
- [ngClass]: Applies priority and completion styles. See [Use NgClass in Templates](/angular/directives/use-ng-class-in-templates).
- titlecase pipe: Capitalizes priority. See [Angular Pipes](/angular/pipes/angular-pipes).
- <app-task-modal></app-task-modal>: Displays a modal with projected content when a task is selected.
Step 5: Create the Custom Dynamic Template Directive
The appDynamicTemplate directive will render an ng-template based on task priority, demonstrating programmatic template instantiation.
Update dynamic-template.directive.ts:
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appDynamicTemplate]'
})
export class DynamicTemplateDirective {
@Input() appDynamicTemplate: TemplateRef | null = null;
@Input() appDynamicTemplateTask: any = null;
constructor(
private viewContainer: ViewContainerRef
) {}
ngOnChanges() {
this.viewContainer.clear();
if (this.appDynamicTemplate && this.appDynamicTemplateTask && this.shouldRender()) {
this.viewContainer.createEmbeddedView(this.appDynamicTemplate, {
$implicit: this.appDynamicTemplateTask,
priority: this.appDynamicTemplateTask.priority
});
}
}
private shouldRender(): boolean {
return this.appDynamicTemplateTask?.priority === 'high';
}
}
- Explanation:
- @Directive: Selector [appDynamicTemplate] applies the directive.
- @Input() appDynamicTemplate: Receives the ng-template to render.
- @Input() appDynamicTemplateTask: Receives the task data.
- ViewContainerRef: Manages the DOM where the template is inserted.
- ngOnChanges: Updates the view when inputs change, rendering the template only for high-priority tasks.
- Template Context: Passes $implicit (default context) and priority to the template.
Apply the directive in task-list.component.html:
{ { task.name }}
Priority: { { task.priority | titlecase }}
{ { task.completed ? 'Mark Incomplete' : 'Mark Complete' }}
View Details
- The taskTemplate is rendered only for high-priority tasks, displaying the alert defined in #taskTemplate.
Step 6: Implement the Task Modal Component
The TaskModalComponent will use ng-template for custom modal content via content projection and TemplateRef.
Update task-modal.component.ts:
import { Component, Input, Output, EventEmitter, ViewChild, TemplateRef } from '@angular/core';
@Component({
selector: 'app-task-modal',
templateUrl: './task-modal.component.html',
styleUrls: ['./task-modal.component.css']
})
export class TaskModalComponent {
@Input() task: any = {};
@Output() close = new EventEmitter();
@ViewChild('defaultContent', { static: true }) defaultContent!: TemplateRef;
@ViewChild('modalContent', { read: TemplateRef }) customContent?: TemplateRef;
get contentTemplate(): TemplateRef {
return this.customContent || this.defaultContent;
}
onClose() {
this.close.emit();
}
}
- Explanation:
- @Input() task: Receives the selected task.
- @Output() close: Emits an event to close the modal.
- @ViewChild: References the default and custom content templates.
- contentTemplate: Returns the custom content (modalContent) if provided, otherwise the default content.
Update task-modal.component.html:
Task Details
Close
{ { task.name }}
Details: { { task.details }}
- Key Features:
- Bootstrap Modal: Styles the dialog with Bootstrap classes.
- ngTemplateOutlet**: Renders the selected template (contentTemplate) with the task as context. See [Use NgTemplate for Dynamic UI](/angular/directives/use-ng-template-for-dynamic-ui).
- #defaultContent: Provides fallback content if no custom content is projected.
- Context: Passes task as $implicit to the template.
Update task-modal.component.css:
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1050;
}
Step 7: Update the App Module
Ensure all components and directives are declared:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { TaskListComponent } from './task-list/task-list.component';
import { TaskModalComponent } from './task-modal/task-modal.component';
import { DynamicTemplateDirective } from './dynamic-template.directive';
@NgModule({
declarations: [
AppComponent,
TaskListComponent,
TaskModalComponent,
DynamicTemplateDirective
],
imports: [BrowserModule, AppRoutingModule],
bootstrap: [AppComponent]
})
export class AppModule {}
Step 8: Test the Application
Run the app:
ng serve --open
- Visit http://localhost:4200 to see the task list:
- ngFor**: Renders task cards in a grid.
- [ngClass]: Applies priority and completion styles.
- ngIf with else**: Shows an empty state or task count.
- appDynamicTemplate: Displays an alert for high-priority tasks using #taskTemplate.
- <app-task-modal></app-task-modal>: Opens a modal with custom content (#modalContent) when “View Details” is clicked.
Test functionality:
- Toggle task completion to update styles.
- Click “View Details” to open the modal with task details.
- Verify high-priority tasks (e.g., “Learn Angular”) show an alert.
- Close the modal to clear the selection.
- Resize the browser to confirm responsiveness.
Step 9: Verify with Unit Tests
Run unit tests:
ng test
- Test DynamicTemplateDirective:
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DynamicTemplateDirective } from './dynamic-template.directive'; import { Component } from '@angular/core'; @Component({ template: ` { { task.name }} ` }) class TestComponent { task = { name: 'Test', priority: 'high' }; } describe('DynamicTemplateDirective', () => { let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [TestComponent, DynamicTemplateDirective] }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); }); it('should render template for high-priority task', () => { const div = fixture.nativeElement.querySelector('div div'); expect(div.textContent).toContain('Test'); }); });
- Test TaskModalComponent:
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TaskModalComponent } from './task-modal.component'; describe('TaskModalComponent', () => { let fixture: ComponentFixture; let component: TaskModalComponent; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [TaskModalComponent] }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(TaskModalComponent); component = fixture.componentInstance; component.task = { name: 'Test', details: 'Details' }; fixture.detectChanges(); }); it('should render default content', () => { const content = fixture.nativeElement.querySelector('.modal-body'); expect(content.textContent).toContain('Test'); }); it('should emit close event', () => { spyOn(component.close, 'emit'); component.onClose(); expect(component.close.emit).toHaveBeenCalled(); }); });
- Learn more in [Test Components with Jasmine](/angular/testing/test-components-with-jasmine).
Best Practices for Using ng-template
- Use with Structural Directives: Pair ng-template with ngIf, ngFor, or *ngSwitch for conditional or iterative rendering. See [Use NgIf in Templates](/angular/directives/use-ngif-in-templates).
- Provide Context: Use let- to pass data to templates (e.g., let-task) for dynamic content.
- Leverage ngTemplateOutlet: Render templates programmatically with context for maximum flexibility.
- Keep Templates Focused: Define small, reusable templates to avoid complexity.
- Optimize Performance: Minimize template rendering in large loops; use trackBy with *ngFor and OnPush change detection. See [Optimize Change Detection](/angular/advanced/optimize-change-detection).
- Test Template Rendering: Write unit tests to verify templates render correctly with different contexts. See [Test Components with Jasmine](/angular/testing/test-components-with-jasmine).
- Document Templates: Comment on expected context variables and usage to guide developers.
Advanced ng-template Techniques
Programmatic Template Rendering
Use TemplateRef and ViewContainerRef in a component:
import { Component, ViewChild, TemplateRef, ViewContainerRef } from '@angular/core';
@Component({
selector: 'app-dynamic',
template: `Dynamic`
})
export class DynamicComponent {
@ViewChild('template', { static: true }) template!: TemplateRef;
constructor(private viewContainer: ViewContainerRef) {}
renderTemplate() {
this.viewContainer.createEmbeddedView(this.template);
}
}
Template with Custom Directives
Combine ng-template with custom directives:
@Directive({
selector: '[appCustom]'
})
export class CustomDirective {
constructor(
private templateRef: TemplateRef,
private viewContainer: ViewContainerRef
) {
this.viewContainer.createEmbeddedView(this.templateRef);
}
}
- See [Create Custom Directives](/angular/directives/create-custom-directives).
Reusable Template Libraries
Package templates in a library:
ng generate library ui-templates
- Export components and directives that use ng-template. See [Create Component Libraries](/angular/libraries/create-component-libraries).
Troubleshooting Common Issues
- Template Not Rendering:
- Ensure ng-template is referenced correctly (e.g., #taskTemplate).
- Verify *ngTemplateOutlet or directive inputs are set (e.g., [appDynamicTemplate]="taskTemplate").
- Context Variables Undefined:
- Check let- declarations (e.g., let-task) and context object (e.g., { $implicit: task }).
- Debug with { { context | json }} in the template.
- Performance Issues:
- Use trackBy in *ngFor loops.
- Minimize template rendering in large datasets.
- Optimize with OnPush change detection. See [Optimize Change Detection](/angular/advanced/optimize-change-detection).
- Modal Not Displaying:
- Ensure *ngIf="selectedTask" evaluates to true.
- Check Bootstrap modal styles are applied correctly.
- Directive Not Working:
- Verify the directive is declared in the module and inputs are bound correctly.
FAQs
What is ng-template in Angular?
ng-template is a directive that defines a reusable HTML template that is not rendered by default, used with structural directives or programmatically for dynamic UI.
How do I use ng-template with structural directives?
Pair ng-template with ngIf, ngFor, or *ngSwitch using else, then, or as a reference (e.g., ). See Use NgIf in Templates.
What’s the difference between ng-template and ng-container?
ng-template defines a dormant template, while ng-container is a wrapper that renders its content without adding extra DOM elements. Use ng-container for grouping without markup.
How do I render ng-template programmatically?
Use TemplateRef and ViewContainerRef to call createEmbeddedView with a context object, or use *ngTemplateOutlet in templates.
Why isn’t my ng-template content rendering?
Ensure the template is referenced (e.g., #template), bound to a directive or ngTemplateOutlet, and the context is provided correctly. Debug with { { context | json }}.
Conclusion
The ng-template directive is a versatile tool for building dynamic, flexible user interfaces in Angular, enabling conditional rendering, reusable templates, and programmatic control. This guide has shown you how to use ng-template in a task management app with built-in directives, a custom directive (appDynamicTemplate), and a modal component with projected content. By following best practices and integrating with Angular’s ecosystem, you can create maintainable, performant, and dynamic UIs that adapt to your application’s needs. With these skills, you’re ready to explore advanced ng-template use cases, package templates into libraries, or apply them to real-world Angular projects.
Start using ng-template in your Angular apps today, and unlock the power of dynamic, reusable UI design!