Reputation: 1284
I need to be able to get template HTML via HTTP request and let a component function off of that HTML.
Use case: The company I work for has software that runs multi-tenant, and needed an angular application that has a portion that can be customized per 'theme' using the software. Because of this, we need to define a default template that will be used whenever there isn't a customized 'themed' version. We use this pattern all over our site to display many views/pages (using liquid)
I don't need to just show HTML grabbed via request, that's easy enough, I need to specifically inject HTML into an angular component that it can do angular things on.
Upvotes: 1
Views: 1235
Reputation: 1284
I actually found a solution that works for us, the only downside is we have to disable AoT compilation for that project. For those that come in after me wanting the same thing - Here's a quick overview of what I ended up doing. First off, found the base for my solution here: https://stackblitz.com/edit/mlc-app-init-zyns9l?file=app%2Fapp.component.ts which I threw into a shared helper:
import { Component, NgModule, Compiler, ViewContainerRef, Type } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { SharedModule } from '../modules/shared.module';
export class DynamicComponentHelper {
public static addComponent<ViewModelType = any>(compiler: Compiler, container: ViewContainerRef, template: string, viewModel?: ViewModelType, components: Array<Type<any>> = []): void {
@Component({ template: template })
class DynamicComponent {
public vm: ViewModelType = viewModel;
constructor() { }
}
components.push(DynamicComponent);
@NgModule({
imports: [BrowserModule, SharedModule],
declarations: components
})
class DynamicComponentModule { }
const mod = compiler.compileModuleAndAllComponentsSync(DynamicComponentModule);
const factory = mod.componentFactories.find((comp) =>
comp.componentType === DynamicComponent
);
const component = container.createComponent(factory);
}
}
I then have a component calling it like so...
export interface VM { text: string; }
@Component({
selector: 'app-component',
template: `<ng-template #dynamicComponent></ng-template>`
...
})
export class VariationsComponent implements OnInit, AfterViewInit {
@ViewChild('dynamicComponent', { read: ViewContainerRef }) _container: ViewContainerRef;
private vm: VariationsComponentVM = { text: 'Hello World' }
private viewInitialized: boolean = false;
private componentTemplate: string;
constructor(private compiler: Compiler) { }
ngAfterViewInit(): void {
this.viewInitialized = true;
this.setUpDynamicComponent();
}
ngOnInit(): void {
this.httpService.getComponentTemplate().subscribe(template => {
this.componentTemplate = template;
this.setUpDynamicComponent();
});
}
private setUpDynamicComponent(): void {
if (this.viewInitialized === true && this.componentTemplate) {
DynamicComponentHelper.addComponent(this.compiler, this._container, this.componentTemplate, this.vm, [NestedComponent]);
}
}
}
And then the actual template being used could be as simple as...
<div>{{ vm.text }}</div>
<app-nested [input]="vm.text"></app-nested>
The great part about this, is all the built in angular templating stuff works, all the directives and everything. Once again, the only downside, is you have to lose AoT. Would it be/is it possible to disable AoT at a component level? It would be nice if we could compile the majority of a project in AoT, but leave a defined one out.
Right now my build commands have to be:
ng build ProjectName --aot=false
ng build ProjectName --prod aot=false --build-optimizer=false
Upvotes: 1