We want to divide our large frontend projects into multiple separately deployed projects which are easier to work with. I am trying to include a bundled ngModule to handle a route from within another app. The apps must be ignorant of each other's configuration. The bundles will share some large dependencies(like Angular) via globals. We don't need to shake across the bundles and we may just have to accept some duplicate dependencies.

The root router complains that

Error: No NgModule metadata found for 'TestsetModule'.

which leads me to believe the child module is not being angular compiled on load, or is not registering its module for some reason. I think it may be necessary to manually compile the module, but I'm not sure how to use this https://angular.io/api/core/Compiler#compileModuleAndAllComponentsAsync

The root app loads the child via a route:

import { ModuleWithProviders } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const load = require("little-loader"); const routes: Routes = [ { path: ``, loadChildren: () => new Promise(function (resolve) { load('http://localhost:3100/testset-module-bundle.js',(err: any) => { console.log('global loaded bundle is: ', (<any>global).TestsetModule ) resolve((<any>global).TestsetModule) } ) })} ]; export const HostRouting: ModuleWithProviders = RouterModule.forRoot(routes);

I also tried using angular router's string resolution syntax rather than this weird global thing you see but I had similar issues.

Here is the module which is being loaded, very standard except for the global export:

import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HttpModule } from '@angular/http'; //import { MaterialModule } from '@angular/material'; import { FlexLayoutModule } from '@angular/flex-layout'; import { FormsModule } from '@angular/forms'; import { LoggerModule, Level } from '@churro/ngx-log'; import { FeatureLoggerConfig } from './features/logger/services/feature-logger-config'; import { TestsetComponent } from './features/testset/testset.component'; import { TestsetRouting } from './testset.routing'; @NgModule({ imports: [ CommonModule, //MaterialModule, FlexLayoutModule, HttpModule, FormsModule, LoggerModule.forChild({ moduleName: 'Testset', minLevel: Level.INFO }), TestsetRouting, ], declarations: [TestsetComponent], providers: [ /* TODO: Providers go here */ ] }) class TestsetModule { } (<any>global).TestsetModule = TestsetModule export {TestsetModule as default, TestsetModule};

Here is the webpack configuration of the root bundle. Note the global exports via the poorly named "ProvidePlugin".

const webpack = require('webpack'); const AotPlugin = require('@ngtools/webpack').AotPlugin; const path = require('path'); const BrowserSyncPlugin = require('browser-sync-webpack-plugin'); const IgnorePlugin = require('webpack/lib/IgnorePlugin'); const PolyfillsPlugin = require('webpack-polyfills-plugin'); const WebpackSystemRegister = require('webpack-system-register'); module.exports = (envOptions) => { envOptions = envOptions || {}; const config = { entry: { 'bundle': './root.ts' }, output: { libraryTarget: 'umd', filename: '[name].js',//"bundle.[hash].js", chunkFilename: '[name]-chunk.js', path: __dirname }, externals: { }, resolve: { extensions: ['.ts', '.js', '.html'], }, module: { rules: [ { test: /\.html$/, loader: 'raw-loader' }, { test: /\.css$/, loader: 'raw-loader' }, ] }, devtool: '#source-map', plugins: [ new webpack.ProvidePlugin({ 'angular': '@angular/core', 'ngrouter': '@angular/router', 'ngxlog':'@churro/ngx-log' }) ] }; config.module.rules.push( { test: /\.ts$/, loaders: [ 'awesome-typescript-loader', 'angular-router-loader', 'angular2-template-loader', 'source-map-loader' ] } ); } return config; };

And here is the webpack configuration of the child bundle. Note the "externals" which look for angular as a global.

module.exports = (envOptions) => { envOptions = envOptions || {}; const config = { entry: { 'testset-module-bundle': './src/index.ts' }, output: { //library: 'TestsetModule', libraryTarget: 'umd', filename: '[name].js',//"bundle.[hash].js", chunkFilename: '[name]-chunk.js', path: path.resolve(__dirname, "dist") }, externals: { //expect these to come from the app that imported us // name to be required : name from global 'angular': '@angular/core', 'ngrouter': '@angular/router', 'ngxlog': '@churro/ngx-log' }, resolve: { extensions: ['.ts', '.js', '.html'], }, module: { rules: [ { test: /\.html$/, loader: 'raw-loader' }, { test: /\.css$/, loader: 'raw-loader' }, ] }, devtool: '#source-map', plugins: [ ] }; config.module.rules.push( { test: /\.ts$/, loaders: [ 'awesome-typescript-loader', 'angular-router-loader', 'angular2-template-loader', 'source-map-loader' ] } ); } return config; };

And for good measure here is my tsconfig file which 'awesome-typescript-loader' reads.

{ "compilerOptions": { "target": "es5", "module": "es2015", "moduleResolution": "node", "sourceMap": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "removeComments": false, "noImplicitAny": true, "suppressImplicitAnyIndexErrors": true, "baseUrl": ".", "rootDir": "src", "outDir": "app", "paths": { "@capone/*": [ "*" ], "@angular/*": [ "node_modules/@angular/*" ], "rxjs/*": [ "node_modules/rxjs/*" ] } }, "exclude": ["node_modules", "src/node_modules", "compiled", "src/dev_wrapper_app"], "angularCompilerOptions": { "genDir": "./compiled", "skipMetadataEmit": true } }

If you're still reading, awesome. I was able to get this working when both bundles are part of the same webpack config and the child module is just a chunk. Angular is designed to do that. But our use case is to have the children and parent be ignorant of each other until runtime.