Angular Dynamic Component Loading - Documentation | www.HtmlElements.com

Angular component templates are not always fixed and, sometimes, an application may need to load new components at runtime. The goal of this help topic is to show how to add an Angular component to a smart-tabs dynamically, but the shown approach can be used with other Smart HTML Elements, too.

Angular 15

smart-tabs (app.component.html)

<smart-tabs [selectedIndex]="selectedIndex" #exampleTabs>
	<smart-tab-item label="TAB 1">Content 1</smart-tab-item>
	<smart-tab-item label="TAB 2"></smart-tab-item>
	<smart-tab-item label="TAB 3">Content 3</smart-tab-item>
	<smart-tab-item label="TAB 4">Content 4</smart-tab-item>
</smart-tabs>
<ng-template #dynamic></ng-template>
					

Our goal is to dynamically inject an Angular component in the content section of the second tab item after the smart-tabs is initialized.

Custom Angular Component (dynamic.component.ts)

The custom Angular component we will be injecting is defined in the file dynamic.component.ts:

import { Component } from "@angular/core";

@Component({
	selector: 'dynamic-component',
	template: '<h2>{{data}}</h2>'
})
export class DynamicComponent { 
	@Input() data: string = 'Dynamically attached component'
}
Now, we will use the ViewContainerRef.createComponent Angular API to dynamically create the component. We will create a DynamicComponent and will move it to a new host element. Finally, we will add that host element to the second tab. We will also dynamically update the DynamicComponent by setting its data property to a new value.

app.component.ts

import { Component, ViewChild, AfterViewInit, ComponentRef, ChangeDetectorRef, ViewContainerRef } from '@angular/core';
import { TabsComponent } from 'smart-webcomponents-angular/tabs';
import { DynamicComponent } from './dynamic.component';

@Component({
	selector: 'app-root',
	templateUrl: './app.component.html',
	styleUrls: ['app.component.css']
})

export class AppComponent implements AfterViewInit {
	@ViewChild('exampleTabs', { read: TabsComponent, static: false }) tabs!: TabsComponent;
    @ViewChild('dynamic', { read: ViewContainerRef })

	private viewRef!: ViewContainerRef;
	
	constructor(
		private cdRef: ChangeDetectorRef
	) {}
  
	selectedIndex: number = 1
	ngAfterViewInit(): void {
		// create a new dynamic component.
		const container = document.createElement('div');
		this.viewRef.clear();
		const componentRef: ComponentRef = this.viewRef.createComponent(DynamicComponent);
		// dynamically add the component to a new container host element.
		container.appendChild(componentRef.location.nativeElement);
		// update tabs.
		this.tabs.update(1, 'Updated Tab', container);
		// detect changes.
		this.cdRef.detectChanges();
		// change the dynamic component's data.
		componentRef.instance.data = "TEST";
	}
}

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { DynamicComponent } from './dynamic.component'
import { TabsModule } from 'smart-webcomponents-angular/tabs';

@NgModule({
	declarations: [AppComponent, DynamicComponent],
	imports: [BrowserModule, TabsModule],
	schemas: [],
	providers: [],
	bootstrap: [AppComponent]
})

export class AppModule { }

Before Angular 13

In order to load components dynamically, Angular's ComponentFactoryResolver will be used.

smart-tabs (app.component.html)

The use of the smart-tabs custom element in our main component is defined in app.component.html:

<smart-tabs [selectedIndex]="selectedIndex" #exampleTabs>
		<smart-tab-item label="TAB 1">Content 1</smart-tab-item>
		<smart-tab-item label="TAB 2"></smart-tab-item>
		<smart-tab-item label="TAB 3">Content 3</smart-tab-item>
		<smart-tab-item label="TAB 4">Content 4</smart-tab-item>
	</smart-tabs>

Our goal is to dynamically inject an Angular component in the content section of the second tab item after the smart-tabs is initialized.

Custom Angular Component (dynamic.component.ts)

The custom Angular component we will be injecting is defined in the file dynamic.component.ts:

import { Component } from "@angular/core";

	@Component({
		selector: 'dynamic-component',
		template: '<h2>Dynamically attached component</h2>'
	})
	export class DynamicComponent { }

DOM Manipulation Service (smart-dom.service.ts)

In order to load the component dynamically, we will need a Service that will load and compile the component at runtime. Below you can find the code of the Service, called smartDomService, including the method loadComponent that we will use in app.component.ts:

import {
		Injectable,
		Injector,
		EmbeddedViewRef,
		ComponentFactoryResolver,
		ApplicationRef
	} from '@angular/core';
	@Injectable()
	export class smartDomService {
		componentRef: any;

		constructor(
			private componentFactoryResolver: ComponentFactoryResolver,
			private appRef: ApplicationRef,
			private injector: Injector
		) { }

		loadComponent(component: any, ownerElement: any) {
			// 1. Create a component reference from the component 
			const componentRef = this.componentFactoryResolver
				.resolveComponentFactory(component)
				.create(this.injector, ownerElement);
			// 2. Attach component to the appRef so that it's inside the ng component tree
			this.appRef.attachView(componentRef.hostView);

			// 3. Get DOM element from component
			const domElement = (componentRef.hostView as EmbeddedViewRef)
				.rootNodes[0] as HTMLElement;

			if (ownerElement) {
				ownerElement.appendChild(domElement);
			}

			this.componentRef = componentRef;

			return { componentRef: componentRef, domElement: domElement }
		}

		destroy() {
			this.appRef.detachView(this.componentRef.hostView);
			this.componentRef.destroy();
		}
	}

Our custom Service makes use of Angular's ComponentFactoryResolver.

App Component and Including the Dynamic Component (app.component.ts)

The app component, which has smart-tabs in its template, imports the aforementioned DynamicComponent and smartDomService. In the ngAfterViewInit callback function, we call the service's loadComponent method in order to inject the dynamic component into a new div element. Then, in the whenReady callback of the smart-tabs' underlying native custom element, we call update with the custom div containing the dynamic Angular component as a third argument.

import { Component, ViewChild, AfterViewInit, Inject, ViewContainerRef } from '@angular/core';
	import { smartDomService } from './smart-dom.service';
	import { TabsComponent } from 'smart-webcomponents-angular/tabs';
	import { DynamicComponent } from './dynamic.component';

	@Component({
		selector: 'app-root',
		templateUrl: './app.component.html',
		styleUrls: ['app.component.css']
	})

	export class AppComponent implements AfterViewInit {
		@ViewChild('exampleTabs', { read: TabsComponent, static: false }) tabs!: TabsComponent;
	
		constructor(@Inject(smartDomService) service) {
			this.service = service;
		}
		selectedIndex: number = 1
		service: smartDomService
		ngAfterViewInit(): void {
			const container = document.createElement('div');
		
			this.service.loadComponent(DynamicComponent, container);

			this.exampleTabs.update(1, 'Updated Tab', container);
		
		}
	}

app.module.ts

In app.module.ts the components, the service and the module smart.tabs.js are imported. Of particular importance is adding DynamicComponent to entryComponents, which defines the set of components to compile when the NgModule is defined, so that they can be dynamically loaded into the view.

import { NgModule } from '@angular/core';
	import { BrowserModule } from '@angular/platform-browser';
	import { FormsModule } from '@angular/forms';

	import { AppComponent } from './app.component';
	import { DynamicComponent } from './dynamic.component'
	import { smartDomService } from './smart-dom.service';
	import { TabsModule } from 'smart-webcomponents-angular/tabs';

	@NgModule({
		declarations: [AppComponent, DynamicComponent],
		imports: [BrowserModule, TabsModule],
		schemas: [],
		providers: [smartDomService],
		bootstrap: [AppComponent]
	})

	export class AppModule { }

Result