Disclaimer: this question has been inspired by "Here is what you need to know about dynamic components in Angular" post on Medium.
There an author shows how one can dynamically generate both a module and a component, put the latter into the former, compile the module on the fly, get a component factory and use it to generate a component via ViewContainerRef.
This works just fine in the basic setup.
Now let's assume we'd like to dynamically generate a component but this time with a template that uses async pipe.
Next, we need to have this thing working in the production environment, which means two things:
- AOT is enabled
- enableProdMode() has been called upon bootstrapping an application
Since Angular compiler is not available, we can provide an instance of a jit compiler for the Compiler token in the application root module.
app.module.ts:
import { BrowserModule } from '@angular/platform-browser';
import {Compiler, NgModule} from '@angular/core';
import {AppComponent } from './app.component';
import {JitCompilerFactory} from '@angular/platform-browser-dynamic';
export function jitCompiler() {
/*
* Cast JitCompilerFactory to any because
* jit compiler factory constructor does not
* accept any arguments according to the @angular/platform-browser-dynamic/src/compiler_factory.d.ts
*/
const klass = (JitCompilerFactory as any);
return new klass([]).createCompiler([
{
useJit: true
}
]);
}
@NgModule({
declarations: [
AppComponent
],
providers: [
{
provide: Compiler,
useFactory: jitCompiler
}
],
imports: [
BrowserModule
],
bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts
import {
AfterViewInit,
ChangeDetectionStrategy,
Compiler,
Component,
NgModule,
ViewContainerRef
} from '@angular/core';
import {Subject} from 'rxjs';
import {CommonModule} from '@angular/common';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent implements AfterViewInit {
constructor(
private readonly compiler: Compiler,
private readonly vcr: ViewContainerRef
) {}
ngAfterViewInit() {
const template = '<span>Hello, {{text$ }}</span>';
const cmp = Component({
template
})(class DynamicComponent {
text$: Subject<string>;
});
const module = NgModule({
declarations: [cmp],
imports: [CommonModule],
providers: []
})(class DynamicModule {});
this.compiler.compileModuleAndAllComponentsAsync(module)
.then(compiledModule => {
const dynamicComponent = this.vcr.createComponent(compiledModule.componentFactories[0]);
const sub = new Subject();
sub.next('World!');
dynamicComponent.instance.text$ = sub;
// Just to simulate some server updates
setTimeout(() => dynamicComponent.instance.text$.next('Another'), 3e3);
});
}
}
If we run that and open up DevTools, we can see the following error:
ERROR Error: Unexpected value 'function(){}' imported by the module 'function(){}'. Please add a @NgModule annotation.
The issue happens only during development mode. Angular version is 7.2.0.
Hence the question - what has to be done to make it work in AOT mode?