I am taking the below information from the following StackOverflow question: http://stackoverflow.com/questions/42537138/angular2-cli-tree-shaking-removing-dynamically-created-ngmodule (I am the author)
any
@angular/cli: 1.0.0-rc.1
node: 6.10.0
os: linux x64
@angular/common: 2.4.9
@angular/compiler: 2.4.9
@angular/core: 2.4.9
@angular/forms: 2.4.9
@angular/http: 2.4.9
@angular/platform-browser: 2.4.9
@angular/platform-browser-dynamic: 2.4.9
@angular/router: 3.4.9
@angular/upgrade: 2.4.9
@angular/cli: 1.0.0-rc.1
@angular/compiler-cli: 2.4.9
typescript: 2.1.6
Compiling my app with a non-production environment works just fine. However, when I run ng build --prod -e prod the transpiling/compiling process removes all dynamically created modules.
Immediately when trying to load a dynamically created component:
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)
My dynamic component builder (the problem is in the createComponentModule function which dynamically creates a new module):
@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;
}
}
And its transpiled/compiled result:
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.
This is the transpiled/compiled content with a non-production setting, still containing all the information about the removed NgModule:
var DynamicTypeBuilder = (function () {
function DynamicTypeBuilder() {
// since this object is a singleton, we can have the cache and the compiler here
this._cacheOfFactories = {};
this.compiler = new __WEBPACK_IMPORTED_MODULE_1__angular_compiler__["a" /* JitCompilerFactory */]([{ useDebug: false, useJit: true }]).createCompiler();
}
DynamicTypeBuilder.prototype.createComponentFactory = function (type, template, additionalModules) {
var _this = this;
if (additionalModules === void 0) { additionalModules = []; }
var factory = this._cacheOfFactories[template];
if (factory) {
return __WEBPACK_IMPORTED_MODULE_2_rxjs__["Observable"].of(factory);
}
var module = this.createComponentModule(type, additionalModules);
return __WEBPACK_IMPORTED_MODULE_2_rxjs__["Observable"].of(this.compiler.compileModuleAndAllComponentsSync(module))
.map(function (moduleWithFactories) {
factory = moduleWithFactories.componentFactories.find(function (value) { return value.componentType == type; });
_this._cacheOfFactories[template] = factory;
return factory;
});
};
DynamicTypeBuilder.prototype.createComponentModule = function (componentType, additionalModules) {
var RuntimeComponentModule = (function () {
function RuntimeComponentModule() {
}
return RuntimeComponentModule;
}());
RuntimeComponentModule = __decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["NgModule"])({
imports: [
__WEBPACK_IMPORTED_MODULE_3__angular_forms__["FormsModule"],
__WEBPACK_IMPORTED_MODULE_3__angular_forms__["ReactiveFormsModule"],
__WEBPACK_IMPORTED_MODULE_4__angular_platform_browser__["BrowserModule"],
__WEBPACK_IMPORTED_MODULE_5_app_modules_pipes_module__["a" /* PipesModule */]
].concat(additionalModules),
declarations: [
componentType
],
schemas: [__WEBPACK_IMPORTED_MODULE_0__angular_core__["CUSTOM_ELEMENTS_SCHEMA"]]
})
], RuntimeComponentModule);
// a module for just this Type
return RuntimeComponentModule;
};
return DynamicTypeBuilder;
}());
DynamicTypeBuilder = __decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["Injectable"])(),
__metadata("design:paramtypes", [])
], DynamicTypeBuilder);
I assume tree shaking removes this dynamically created NgModule.
How can I create a new NgModule like this and still use the production mode of Angular CLI?
Please, anyone?
I don't seem to be the only one having this issue according to some StackOverflow questions I've seen.
+1
+1 Have the same issue when build in prod mode, whereas dev mode works fine
The issue arises when I'm using ng build -aot, even id dev mode. For some reason it removes TypeScript metadata. See the results
main.bundle.js when ng build
...
var AppModule = (function () {
function AppModule() {
}
return AppModule;
}());
AppModule = __decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["c" /* NgModule */])({
imports: [
__WEBPACK_IMPORTED_MODULE_1__angular_platform_browser__["a" /* BrowserModule */],
__WEBPACK_IMPORTED_MODULE_3__angular_http__["a" /* HttpModule */],
__WEBPACK_IMPORTED_MODULE_2__angular_router__["a" /* RouterModule */],
__WEBPACK_IMPORTED_MODULE_5__routing_module__["a" /* AppRoutingModule */]
],
declarations: [__WEBPACK_IMPORTED_MODULE_4__app_component__["a" /* AppComponent */]],
exports: [],
bootstrap: [__WEBPACK_IMPORTED_MODULE_4__app_component__["a" /* AppComponent */]],
})
], AppModule);
...
main.bundle.js when ng build -aot
...
var AppModule = (function () {
function AppModule() {
}
return AppModule;
}());
...
AFAIK this will never work on AOT, because it requires code to be statically analyzable.
You can still build for production without AOT by doing ng prod --prod --no-aot.
You can still build for production without AOT by doing ng prod --prod --no-aot
But why should we want that? I've got a quite large application and aot is ten times faster (initial load only has a performance boost of 6 to 8 seconds).
Are you saying we need to stick to JIT compiler if we need to dynamically add component template html and no AOT will be possible....ever?
Some people have managed to use AOT and have the JitCompiler be shipped with it to be able to compile components at runtime, see
https://github.com/angular/angular/issues/15510#issuecomment-295419915
https://github.com/shlomiassaf/lazy-jit/
I think this issue (or a new one more specifically on making it possible to create an app supporting AOT + JIT) should be reopened.
To be fair the user who came up with the solution over there did not use the CLI but handcrafted it using Webpack.
So, now the task is to transfer his solution to the CLI!
@maxbellec Your second link is not about AOT compilation, please remove it.
I managed to workthrough this by using the AOT compiled componentNgFactory instead of the componentFactories element of the compileModuleAndAllComponentsSync call result as the createComponent's componentFactory parameter.
https://github.com/KingMario/leaflet-popup, a demo of interactive dynamic Angular component content for Leaflet popup created by componentFactory.
Here is the demo. When you click the marker, the popup content will change randomly.
The git diff for the corresponding file: https://github.com/KingMario/leaflet-popup/commit/1fc67935529db49b7d891e0fc16ae977302873bd#diff-5c26d2c8f8838f32ace87fdecd6344ea
Other changes in this commit are for my own AOT compilation case study only. You may run ng build --prod -e prod directly after you generated the componentNgFactories.
Hope it will help you out.
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
_This action has been performed automatically by a bot._
Most helpful comment
Some people have managed to use AOT and have the JitCompiler be shipped with it to be able to compile components at runtime, see
https://github.com/angular/angular/issues/15510#issuecomment-295419915
https://github.com/shlomiassaf/lazy-jit/
I think this issue (or a new one more specifically on making it possible to create an app supporting AOT + JIT) should be reopened.