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. 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 { smartTabs } from '@smarthtmlelements/smart-elements/source/modules/smart.tabs.js';
	import { DynamicComponent } from './dynamic.component';

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

	export class AppComponent implements AfterViewInit {
		@ViewChild("exampleTabs") exampleTabs: smartTabs
		constructor(@Inject(smartDomService) service) {
			this.service = service;
		}
		selectedIndex: number = 1
		service: smartDomService
		ngAfterViewInit(): void {
			const container = document.createElement('div'),
				tabs = this.exampleTabs.nativeElement;

			this.service.loadComponent(DynamicComponent, container);

			tabs.whenReady(() => {
				tabs.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, CUSTOM_ELEMENTS_SCHEMA } 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 '@smarthtmlelements/smart-elements/source/modules/smart.tabs.js';

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

	export class AppModule { }

Result

The source code presented in this tutorial can be found in the file angular-dynamic-component-loading.zip. Here is what the application looks like when launched: