Recently, I developed a component that has the ability to load a child component dynamically with a template retrieved from the server. If you are interested in similar topics, check out this discussion here:
Compile dynamic HTML in Angular 4/5 - something similar to $compile in Angular JS
I want to be able to call this component from any part of my application. Its selector should work within a modal template without causing multiple reloads (I can't refresh the modal repeatedly). It should also function when invoked from a browser window (without simultaneously loading both the modal and browser).
@Component({
selector: 'my-component',
template: `<h2>The following content will be dynamically created and injected<h2>
<div #vc></div>`
})
export class TaggedDescComponent {
@ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef;
private cmpRef: ComponentRef<any>;
constructor(private compiler: Compiler,
private injector: Injector,
private moduleRef: NgModuleRef<any>,
private backendService: backendService,
) {}
ngAfterViewInit() {
// Retrieve HTML content from the backend.
this.backendService.getHTMLFromServer()
.subscribe(rawHTML => this.createComponentFromRaw(rawHTML));
}
// Create the dynamic component here.
private createComponentFromRaw(template: string) {
// Assuming your template resembles `<h2><some-component [data]="data"></some-component>`
// It includes an angular component `some-component` with injected data.
// Now we define a new component. It utilizes the given template and can receive data input.
const tmpCmp = Component({ template, styles })(class {
// This anonymous class behaves like a standard angular class. You can incorporate @Inputs,
// @Outputs, and inject dependencies.
data: { some: 'data'};
ngOnInit() { /* Add functionality to the dynamic component */}
});
// Generate a dynamic module as well.
const tmpModule = NgModule({
imports: [RouterModule],
declarations: [tmpCmp],
// providers: [] - Include necessary services if required by the dynamic component.
})(class {});
// Compile the module and component, then insert it into the #vc container within the current component template.
this.compiler.compileModuleAndAllComponentsAsync(tmpModule)
.then((factories) => {
const f = factories.componentFactories[0];
this.cmpRef = f.create(this.injector, [], null, this.moduleRef);
this.cmpRef.instance.name = 'my-dynamic-component';
this.vc.insert(this.cmpRef.hostView);
});
}
// Implement cleanup operations. Additional clean-up procedures can be added here.
ngOnDestroy() {
if(this.cmpRef) {
this.cmpRef.destroy();
}
}
}
However, when this section is reloaded (without refreshing the browser), I encounter the following error:
ERROR Error: Type e is part of the declarations of 2 modules: function(){} and function(){}! Please consider moving e to a higher module that imports function(){} and function(){}. You can also create a new NgModule that exports and includes e then import that NgModule in function(){} and function(){}.
at le (main.js:1)
at e._addTypeToModule (main.js:1)
at main.js:1
at Array.forEach (<anonymous>)
at e.getNgModuleMetadata (main.js:1)
at e._loadModules (main.js:1)
at e._compileModuleAndAllComponents (main.js:1)
at e.compileModuleAndAllComponentsAsync (main.js:1)
at e.compileModuleAndAllComponentsAsync (main.js:1)
at e.createComponentFromRaw (main.js:1)