1

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:

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?

  • This reads that you want JIT and AOT at the same time. Yes? – R. Richards Apr 17 '19 at 21:06
  • I see what you mean. The article says angular compiler is called JIT during development mode and AOT when compiling all the assets in advance. The aim is to be able to dynamically create both angular module and a component in AOT mode whether it's with JIT or with other solution. – Viacheslav Moskalenko Apr 17 '19 at 21:17
  • The article says about the compiler: *If it’s used during runtime in a browser it is JIT. If you compile your components before the code is executed in a browser, it’s AOT compilation. The latter is a preferred method...*. **During runtime in a browser** seems different than *during development mode* as you put it. It sounds like you want to create and compile modules/components at run-time, in the browser, JIT, but would like to compile to AOT, too. That isn't possible. – R. Richards Apr 17 '19 at 21:46
  • Let me try to clarify this a bit. The idea is to satisfy purely academical interest with being able to dynamically compile modules and components during runtime in a browser in the application that has been built in AOT mode. The article says we can easily load a compiler instance at runtime and use it, which is what I did. I am just not sure what else has to be done in order to be able to use async pipe inside a template of a dynamically generated component. Hope, that cleared the dust a little bit. – Viacheslav Moskalenko Apr 18 '19 at 12:22
  • Just for the sake of clarity - the answer to this question demonstrates it is possible (https://stackoverflow.com/questions/49249919/angular-aot-and-jit-on-same-project), however, I don't see an obvious fix so that it becomes possible to use async pipe for inside a template of a dynamically generated component. – Viacheslav Moskalenko Apr 18 '19 at 14:19
  • This question mixes a few concepts together. Can you create a component dynamically with a pipe in JIT mode? Second, AOT mode is for components that you about at build-time. Hence it doesn't make sense to AOT compile code that creates a component on the fly, if you know about the component you need beforehand, separate into a standalone file and work with it as with any other component – Max Koretskyi Apr 29 '19 at 09:07
  • @MaxKoretskyiakaWizard, let me clarify things a bit: 1) Yes, I can create a dynamic component in JIT mode using a pipe in the component's template 2) Do I understand correctly that AOT isn't applicable in case if application fetches some component templates from the database (say via REST call) during runtime (for example, upon navigating to a certain route)? Yes, I know it's ugly and messy but that's an unfortunate reality of some enterprise projects where one small UI team is only responsible for web app itself while templates are prepared by another team by another vendor. – Viacheslav Moskalenko Apr 30 '19 at 08:50
  • @ViacheslavMoskalenko, you should AOT compile those components before fetching them and work with factories directly. At least that's how I did it a year ago, maybe something have changed – Max Koretskyi May 07 '19 at 19:10
  • @MaxKoretskyiakaWizard, so the algorithm is as follows: * AOT compile all the components that have their templates stored in a db * use produced factories to dynamically generate a component in runtime using, say, view container ref Did I get it right? – Viacheslav Moskalenko May 09 '19 at 18:18
  • @ViacheslavMoskalenko, yeah, you'll have component factories directly and viewcontainerref takes a factory as a parameter – Max Koretskyi May 12 '19 at 11:34
  • @MaxKoretskyiakaWizard, got it. I'll try that, thanks. Until that, I propose to keep the question open. – Viacheslav Moskalenko May 12 '19 at 18:32

0 Answers0