Skip to content

Instantly share code, notes, and snippets.

@isc30
Last active Jul 29, 2021
Embed
What would you like to do?
Angular Dynamic Component
import { Component, ComponentFactoryResolver, Input, OnInit, ViewContainerRef, Type, AfterViewInit, ComponentRef, ChangeDetectorRef, ViewChild, TemplateRef, Injector } from '@angular/core';
import { checkRequiredInputs, Required } from '../../decorators/required.decorator';
export interface DynamicComponentModel<T = unknown>
{
type: Type<T>;
inputs?: Partial<T>;
}
@Component({
selector: 'app-dynamic',
template: '<ng-container #target></ng-container><ng-template #content><ng-content></ng-content></ng-template>'
})
export class DynamicComponent implements OnInit, AfterViewInit
{
@ViewChild('target', { read: ViewContainerRef }) private target!: ViewContainerRef;
@ViewChild('content') private content!: TemplateRef<unknown>;
private cmpRef?: ComponentRef<unknown>;
private isViewInitialized = false;
@Input() @Required() model!: DynamicComponentModel;
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private cdRef: ChangeDetectorRef,
private injector: Injector
) { }
ngOnInit(): void
{
checkRequiredInputs(this);
}
updateComponent()
{
if (!this.isViewInitialized)
{
return;
}
if (this.cmpRef == null
|| !(this.cmpRef.instance instanceof this.model.type))
{
this.cmpRef?.destroy();
this.target.clear();
const factory = this.componentFactoryResolver.resolveComponentFactory(this.model.type);
this.cmpRef = this.target.createComponent(
factory, 0, this.injector,
[ this.content.createEmbeddedView(this).rootNodes ]);
}
Object.assign(this.cmpRef.instance, this.model.inputs);
this.cdRef.detectChanges();
}
ngOnChanges()
{
this.updateComponent();
}
ngAfterViewInit()
{
this.isViewInitialized = true;
setTimeout(() => this.updateComponent(), 0); // defer initial render
}
ngOnDestroy()
{
this.cmpRef?.destroy();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment