Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 (CLI) tree shaking removing dynamically created NgModule

Tags:

I assume the question at Angular-cli tree-shaking exclude component from removal is very similar but I can't seem to get anything out of it.

Essentially I have a dynamic component factory as described in How can I use/create dynamic template to compile dynamic Component with Angular 2.0?.

When I build it using the latest Angular CLI with a non-production setting, it all works fine. However, once I use the production setting I get the following error trace in the browser immediately when trying to load a page that has dynamically created content:

EXCEPTION: No NgModule metadata found for 'e'.
ORIGINAL STACKTRACE:
main.dc05ae9….bundle.js:formatted:4731
Error: No NgModule metadata found for 'e'.
at f (vendor.c18e6df….bundle.js:formatted:76051)
at t.resolve (vendor.c18e6df….bundle.js:formatted:20624)
at t.getNgModuleMetadata (vendor.c18e6df….bundle.js:formatted:20169)
at t._loadModules (vendor.c18e6df….bundle.js:formatted:40474)
at t._compileModuleAndAllComponents (vendor.c18e6df….bundle.js:formatted:40462)
at t.compileModuleAndAllComponentsSync (vendor.c18e6df….bundle.js:formatted:40436)
at e.createComponentFactory (main.dc05ae9….bundle.js:formatted:4789)

Here is my component factory class:

@Injectable() export class DynamicTypeBuilder {       constructor() {   }    private _cacheOfFactories: {[templateKey: string]: ComponentFactory<any>} = {};   private compiler: Compiler = new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler();    public createComponentFactory<COMPONENT_TYPE>(type: any, template: string, additionalModules: any[] = []): Observable<ComponentFactory<COMPONENT_TYPE>> {      let factory = this._cacheOfFactories[template];     if (factory) {       return Observable.of(factory);     }      // unknown template ... let's create a Type for it     let module = this.createComponentModule(type, additionalModules);      // compiles and adds the created factory to the cache     return Observable.of(this.compiler.compileModuleAndAllComponentsSync(module))                      .map((moduleWithFactories: ModuleWithComponentFactories<COMPONENT_TYPE>) => {                        factory = moduleWithFactories.componentFactories.find(value => value.componentType == type);                        this._cacheOfFactories[template] = factory;                                                   return factory;                      });   }    protected createComponentModule(componentType: any, additionalModules: any[]): Type<any> {     @NgModule({       imports: [         FormsModule,         ReactiveFormsModule,         BrowserModule,         PipesModule,         ...additionalModules       ],       declarations: [         componentType       ],       schemas:[CUSTOM_ELEMENTS_SCHEMA]     })     class RuntimeComponentModule {     }      return RuntimeComponentModule;   } } 

which is being transpiled to

var _ = function() {     function e() {         this._cacheOfFactories = {},         this.compiler = new i.a([{             useDebug: !1,             useJit: !0         }]).createCompiler()     }     return e.prototype.createComponentFactory = function(e, t, n) {         var i = this;         var _ = this._cacheOfFactories[t];         if (_)             r.Observable.of(_);         var a = this.createComponentModule(e, n);         return r.Observable.of(this.compiler.compileModuleAndAllComponentsSync(a)).map(function(n) {             return _ = n.componentFactories.find(function(t) {                 return t.componentType == e             }),             i._cacheOfFactories[t] = _,             _         })     }     ,     e.prototype.createComponentModule = function(e, t) {         var n = function() {             function e() {}             return e         }();         return n     }     ,     e.ctorParameters = function() {         return []     }     ,     e }() 

The 'e' in the error message is the function e() from createComponentModule which, as you can see, is empty even though is should contain the @NgModule content.

How can I create a new NgModule dynamically and still use the production mode of Angular CLI?

Versions:
Angular2: 2.4.8
Angular CLI: 1.0.0-beta.32.3
TypeScript: 2.1.6

like image 402
Sebastian Avatar asked Mar 01 '17 16:03

Sebastian


1 Answers

Unfortunately it really seems like this is impossible at the moment (I will try to keep the answer up to date), neither with Angular 2.x nor with Angular 4 beta.
The problem being that a dynamic component definition contains file references (template, style sheets) which cannot be resolved any more at run time with the AOT compiler having run before.
But also if the component or module would not contain file references the current Angular code does not allow for a truly dynamic creation of components. It just doesn't find the metadata that is being created at runtime.

Summarizing the problem, there are 3 levels of dynamic component creation:

  1. Define a component statically and include it in an NgModule that the AOT compiler can find at AOT compile time. Such a component can at any point be instantiated without problems. (See ComponentFactoryResolver etc)
  2. Define the body of a component statically (code etc) but allow to have dynamic templates and/or styles (i.e. template is being created in code just when needed). This also requires an NgModule to be compiled at runtime. This is currently only possible when not using the AOT compiler and represents the issue I posted here.
  3. Define the full component dynamically, including code and templates. This is not what is intended here and might even go to far. But possibly someone has this issue as well.

In my opinion the number 2 issue can be solved. The Angular team says since it is AOT it can only compile those things that are statically known at AOT compile time, but I disagree.
I could think of the possibility to AOT compile a 'stub' of such a component which is then being instantiated with a dynamic template or style sheet when needed. There might be the need to use a new property for the @Component annotation or a totally new annotation like @DynamicComponent but it seems feasible to me. I do not know if the same changes would be required for @NgModule declaration, but I assume they would.

like image 87
Sebastian Avatar answered Sep 20 '22 12:09

Sebastian