Dynamic Components
Dynamic component loading in Angular is an advanced feature that allows you to decide which components to render at runtime. This is useful in scenarios such as displaying different interfaces based on user permissions, rendering components based on data results, or implementing modular loading to improve performance.
Preparation
Ensure you have an Angular environment. If not, create a new project using Angular CLI:
ng new dynamic-components-demo
cd dynamic-components-demoCreating Components
To demonstrate dynamic component loading, create three components:
ng generate component component-a
ng generate component component-b
ng generate component component-cCreating a Container Component
Dynamic components typically require a container component to manage loading and unloading. Create a container component:
ng generate component containerIn container.component.html, add a placeholder for dynamically loaded components:
<!-- container.component.html -->
<div #dynamicContainer></div>Using ViewContainerRef
ViewContainerRef is a key Angular interface for dynamically inserting and removing views. In container.component.ts, inject ViewContainerRef and create a method to load components dynamically:
import { Component, ViewContainerRef, ComponentFactoryResolver, OnInit } from '@angular/core';
import { ComponentAComponent } from '../component-a/component-a.component';
import { ComponentBComponent } from '../component-b/component-b.component';
import { ComponentCComponent } from '../component-c/component-c.component';
@Component({
selector: 'app-container',
templateUrl: './container.component.html',
styleUrls: ['./container.component.css']
})
export class ContainerComponent implements OnInit {
constructor(
private viewContainerRef: ViewContainerRef,
private componentFactoryResolver: ComponentFactoryResolver
) {}
ngOnInit(): void {
this.loadComponent('ComponentAComponent');
}
loadComponent(componentName: string): void {
let componentFactory;
switch (componentName) {
case 'ComponentAComponent':
componentFactory = this.componentFactoryResolver.resolveComponentFactory(ComponentAComponent);
break;
case 'ComponentBComponent':
componentFactory = this.componentFactoryResolver.resolveComponentFactory(ComponentBComponent);
break;
case 'ComponentCComponent':
componentFactory = this.componentFactoryResolver.resolveComponentFactory(ComponentCComponent);
break;
default:
throw new Error(`Unknown component: ${componentName}`);
}
this.viewContainerRef.clear(); // Clear existing components
const componentRef = this.viewContainerRef.createComponent(componentFactory);
}
}Ensure you import the components (ComponentAComponent, ComponentBComponent, ComponentCComponent) as shown.
Dynamically Loading Components
The loadComponent method enables dynamic loading of components based on user actions or data results.
Dynamic Component Loading with Routing
Another common approach is using Angular routing for dynamic component loading. In app-routing.module.ts, define routes for dynamic loading:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ContainerComponent } from './container/container.component';
const routes: Routes = [
{ path: 'component-a', component: ContainerComponent, data: { component: 'ComponentAComponent' } },
{ path: 'component-b', component: ContainerComponent, data: { component: 'ComponentBComponent' } },
{ path: 'component-c', component: ContainerComponent, data: { component: 'ComponentCComponent' } },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }In container.component.ts, use route data to determine which component to load:
import { Component, ViewContainerRef, ComponentFactoryResolver, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ComponentAComponent } from '../component-a/component-a.component';
import { ComponentBComponent } from '../component-b/component-b.component';
import { ComponentCComponent } from '../component-c/component-c.component';
@Component({
selector: 'app-container',
templateUrl: './container.component.html',
styleUrls: ['./container.component.css']
})
export class ContainerComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private viewContainerRef: ViewContainerRef,
private componentFactoryResolver: ComponentFactoryResolver
) {}
ngOnInit(): void {
this.route.data.subscribe(data => {
this.loadComponent(data['component']);
});
}
loadComponent(componentName: string): void {
let componentFactory;
switch (componentName) {
case 'ComponentAComponent':
componentFactory = this.componentFactoryResolver.resolveComponentFactory(ComponentAComponent);
break;
case 'ComponentBComponent':
componentFactory = this.componentFactoryResolver.resolveComponentFactory(ComponentBComponent);
break;
case 'ComponentCComponent':
componentFactory = this.componentFactoryResolver.resolveComponentFactory(ComponentCComponent);
break;
default:
throw new Error(`Unknown component: ${componentName}`);
}
this.viewContainerRef.clear();
const componentRef = this.viewContainerRef.createComponent(componentFactory);
}
}Using Lazy Loading
Lazy loading is a performance optimization technique that loads modules or components only when needed. In the routing configuration, use loadChildren for lazy loading:
const routes: Routes = [
{ path: 'lazy', loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule) },
];In lazy.module.ts, define the lazy-loaded component:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LazyComponent } from './lazy.component';
@NgModule({
declarations: [LazyComponent],
imports: [
CommonModule,
],
exports: [LazyComponent]
})
export class LazyModule { }Summary
Dynamic component loading is a powerful and flexible Angular feature, enabling complex UI scenarios. Using ViewContainerRef and ComponentFactoryResolver, you can load and unload components at runtime. Combined with Angular routing and lazy loading, you can further optimize application performance and user experience.
Content Projection with ng-content
Content projection is a key Angular feature that allows parent components to inject arbitrary content into child components. This enhances component flexibility and reusability, as components can accept external content without knowing its specifics. The ng-content directive facilitates content projection in Angular.
Basic Concepts
Content projection involves inserting content from a parent component into a child component’s template, typically using the ng-content directive in the child’s template.
Using ng-content
The ng-content directive selects content to project. In a child component’s template, the <ng-content> tag indicates where projected content should be inserted.
For example, create a HighlightBoxComponent with a styled box to display arbitrary content:
<!-- highlight-box.component.html -->
<div class="highlight-box">
<ng-content></ng-content>
</div>In the parent component, use HighlightBoxComponent:
<!-- parent.component.html -->
<app-highlight-box>
<p>This is a paragraph.</p>
<button>Click me</button>
</app-highlight-box>Multiple ng-content Tags
Use multiple ng-content tags to organize different content from the parent component, distinguished by the select attribute.
Modify HighlightBoxComponent to accept a title and body content:
<!-- highlight-box.component.html -->
<div class="highlight-box">
<h2><ng-content select="h2"></ng-content></h2>
<div><ng-content></ng-content></div>
</div>In the parent component:
<!-- parent.component.html -->
<app-highlight-box>
<h2>Title</h2>
<p>This is the body content.</p>
</app-highlight-box>Controlling Projection with *ngIf
Conditionally display projected content using *ngIf on ng-content:
<!-- highlight-box.component.html -->
<div class="highlight-box">
<ng-content *ngIf="showContent"></ng-content>
</div>Control showContent in the component class:
import { Component } from '@angular/core';
@Component({
selector: 'app-highlight-box',
templateUrl: './highlight-box.component.html',
})
export class HighlightBoxComponent {
showContent = true;
}Styling and Attributes for Projected Content
Projected content inherits styles and attributes from the parent component. To apply child component styles to projected content, use the ::ng-deep pseudo-class:
/* highlight-box.component.css */
.highlight-box {
background-color: yellow;
}
/* Apply styles to projected content */
.highlight-box ::ng-deep p {
color: red;
}Order of Projected Content
Projected content is inserted in the order it appears in the parent component. Multiple ng-content tags are filled sequentially based on this order.
Projecting with ng-template
Use ng-template for projecting complex structures:
<!-- parent.component.html -->
<app-highlight-box>
<ng-template #title>
<h2>Title</h2>
</ng-template>
<ng-template #content>
<p>This is the body content.</p>
</ng-template>
</app-highlight-box>In the child component:
<!-- highlight-box.component.html -->
<div class="highlight-box">
<ng-content select="[title]"></ng-content>
<ng-content select="[content]"></ng-content>
</div>Limitations of Projection
While ng-content is powerful, it has limitations, such as not supporting event bindings or two-way data binding for projected content. Use input properties or custom events to address these limitations.



