Javascript 使用 Angular CLI 和 Angular 5 在运行时动态加载新模块
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 
原文地址: http://stackoverflow.com/questions/50149016/
Warning: these are provided under cc-by-sa 4.0 license.  You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Load new modules dynamically in run-time with Angular CLI & Angular 5
提问by Lars Meijdam
Currently I'm working on a project which is being hosted on a clients server. For new 'modules' there is no intention to recompilethe entire application. That said, the client wants to update the router/lazy loaded modules in runtime. I've tried several things out but I can't get it to work. I was wondering if any of you knows what I could still try or what I missed.
目前我正在处理一个托管在客户端服务器上的项目。对于新的“模块”,不打算重新编译整个应用程序。也就是说,客户端想要在运行时更新路由器/延迟加载的模块。我已经尝试了几件事,但我无法让它工作。我想知道你们中是否有人知道我还可以尝试什么或我错过了什么。
One thing I noticed, most of the resources I tried, using angular cli, are being bundled into seperate chunks by webpack by default when building the application. Which seems logical as it makes use of the webpack code splitting. but what if the module is not known yet at compile time (but a compiled module is stored somewhere on a server)? The bundling does not work because it can't find the module to import. And Using SystemJS will load up UMD modules whenever found on the system, but are also bundled in a seperate chunk by webpack.
我注意到的一件事是,我使用 angular cli 尝试的大部分资源在构建应用程序时默认被 webpack 捆绑到单独的块中。这似乎合乎逻辑,因为它使用了 webpack 代码拆分。但是如果模块在编译时还不知道怎么办(但是编译的模块存储在服务器上的某个地方)怎么办?捆绑不起作用,因为它找不到要导入的模块。只要在系统上找到 UMD 模块,使用 SystemJS 就会加载它们,但也会被 webpack 捆绑在一个单独的块中。
Some resources I already tried;
我已经尝试过的一些资源;
- dynamic-remote-component-loader
 - module-loading
 - Loading modules from different server at runtime
 - How to load dynamic external components into Angular application
 - Implementing a plugin architecture / plugin system / pluggable framework in Angular 2, 4, 5, 6
 - Angular 5 - load modules (that are not known at compile time) dynamically at run-time
 - https://medium.com/@nikolasleblanc/building-an-angular-4-component-library-with-the-angular-cli-and-ng-packagr-53b2ade0701e
 - Some several other relating this topic.
 
- 动态远程组件加载器
 - 模块加载
 - 在运行时从不同的服务器加载模块
 - 如何将动态外部组件加载到 Angular 应用程序中
 - 在 Angular 2、4、5、6 中实现插件架构/插件系统/可插拔框架
 - Angular 5 - 在运行时动态加载模块(在编译时未知)
 - https://medium.com/@nikolasleblanc/building-an-angular-4-component-library-with-the-angular-cli-and-ng-packagr-53b2ade0701e
 - 其他一些与此主题相关的内容。
 
Some code I already tried and implement, but not working at this time;
我已经尝试并实现了一些代码,但此时无法正常工作;
Extending router with normal module.ts file
使用普通的 module.ts 文件扩展路由器
  this.router.config.push({
    path: "external",
    loadChildren: () =>
      System.import("./module/external.module").then(
        module => module["ExternalModule"],
        () => {
          throw { loadChunkError: true };
        }
      )
  });
Normal SystemJS Import of UMD bundle
UMD 包的普通 SystemJS 导入
System.import("./external/bundles/external.umd.js").then(modules => {
  console.log(modules);
  this.compiler.compileModuleAndAllComponentsAsync(modules['External'])
    .then(compiled => {
      const m = compiled.ngModuleFactory.create(this.injector);
      const factory = compiled.componentFactories[0];
      const cmp = factory.create(this.injector, [], null, m);
    });
});
Import external module, not working with webpack (afaik)
导入外部模块,不适用于 webpack (afaik)
const url = 'https://gist.githubusercontent.com/dianadujing/a7bbbf191349182e1d459286dba0282f/raw/c23281f8c5fabb10ab9d144489316919e4233d11/app.module.ts';
const importer = (url:any) => Observable.fromPromise(System.import(url));
console.log('importer:', importer);
importer(url)
  .subscribe((modules) => {
    console.log('modules:', modules, modules['AppModule']);
    this.cfr = this.compiler
      .compileModuleAndAllComponentsSync(modules['AppModule']);
    console.log(this.cfr,',', this.cfr.componentFactories[0]);
    this.external.createComponent(this.cfr.componentFactories[0], 0);
});
Use SystemJsNgModuleLoader
使用 SystemJsNgModuleLoader
this.loader.load('app/lazy/lazy.module#LazyModule')
  .then((moduleFactory: NgModuleFactory<any>) => {
    console.log(moduleFactory);
    const entryComponent = (<any>moduleFactory.moduleType).entry;
    const moduleRef = moduleFactory.create(this.injector);
    const compFactory = moduleRef.componentFactoryResolver
      .resolveComponentFactory(entryComponent);
  });
Tried loading a module made with rollup
尝试加载一个用 rollup 制作的模块
this.http.get(`./myplugin/${metadataFileName}`)
  .map(res => res.json())
  .map((metadata: PluginMetadata) => {
    // create the element to load in the module and factories
    const script = document.createElement('script');
    script.src = `./myplugin/${factoryFileName}`;
    script.onload = () => {
      //rollup builds the bundle so it's attached to the window 
      //object when loaded in
      const moduleFactory: NgModuleFactory<any> = 
        window[metadata.name][metadata.moduleName + factorySuffix];
      const moduleRef = moduleFactory.create(this.injector);
      //use the entry point token to grab the component type that 
      //we should be rendering
      const compType = moduleRef.injector.get(pluginEntryPointToken);
      const compFactory = moduleRef.componentFactoryResolver
        .resolveComponentFactory(compType); 
// Works perfectly in debug, but when building for production it
// returns an error 'cannot find name Component of undefined' 
// Not getting it to work with the router module.
    }
    document.head.appendChild(script);
  }).subscribe();
Example with SystemJsNgModuleLoader only works when the Module is already provided as 'lazy' route in the RouterModule of the app (which turns it into a chunk when built with webpack)
SystemJsNgModuleLoader 的示例仅在模块已经在应用程序的 RouterModule 中作为“惰性”路由提供时才有效(使用 webpack 构建时将其变成块)
I found a lot of discussion about this topic on StackOverflow here and there and provided solutions seem really good of loading modules/components dynamically if known up front. but none is fitting for our use case of the project. Please let me know what I can still try or dive into.
我在这里和那里在 StackOverflow 上发现了很多关于这个主题的讨论,如果预先知道,提供的解决方案似乎非常适合动态加载模块/组件。但没有一个适合我们的项目用例。请让我知道我仍然可以尝试或深入研究什么。
Thanks!
谢谢!
EDIT: I've found; https://github.com/kirjs/angular-dynamic-module-loadingand will give this a try.
编辑:我发现了;https://github.com/kirjs/angular-dynamic-module-loading并将尝试一下。
UPDATE: I've created a repository with an example of loading modules dynamically using SystemJS (and using Angular 6); https://github.com/lmeijdam/angular-umd-dynamic-example
更新:我创建了一个存储库,其中包含一个使用 SystemJS(并使用 Angular 6)动态加载模块的示例;https://github.com/lmeijdam/angular-umd-dynamic-example
回答by Michael
I was facing the same problem. As far as I understand it until now:
我面临同样的问题。到目前为止,据我了解:
Webpack puts all resources in a bundle and replaces all System.importwith __webpack_require__. Therefore, if you want to load a module dynamically at runtime by using SystemJsNgModuleLoader, the loader will search for the module in the bundle. If the module does not exist in the bundle, you will get an error. Webpack is not going to ask the server for that module. This is a problem for us, since we want to load a module that we do not know at build/compile time.
What we need is loader that will load a module for us at runtime (lazy and dynamic). In my example, I am using SystemJS and Angular 6 / CLI. 
Webpack 将所有资源放在一个包中,并将所有资源替换System.import为__webpack_require__. 因此,如果您想在运行时使用 SystemJsNgModuleLoader 动态加载模块,加载器将在包中搜索模块。如果捆绑包中不存在该模块,您将收到错误消息。Webpack 不会向服务器询问该模块。这对我们来说是一个问题,因为我们想要加载一个在构建/编译时我们不知道的模块。我们需要的是加载器,它会在运行时为我们加载一个模块(惰性和动态)。在我的示例中,我使用 SystemJS 和 Angular 6 / CLI。
- Install SystemJS: npm install systemjs –save
 - Add it to angular.json: "scripts": [ "node_modules/systemjs/dist/system.src.js"]
 
- 安装 SystemJS: npm install systemjs –save
 - 将它添加到 angular.json: "scripts": [ "node_modules/systemjs/dist/system.src.js"]
 
app.component.ts
app.component.ts
import { Compiler, Component, Injector, ViewChild, ViewContainerRef } from '@angular/core';
import * as AngularCommon from '@angular/common';
import * as AngularCore from '@angular/core';
declare var SystemJS;
@Component({
  selector: 'app-root',
  template: '<button (click)="load()">Load</button><ng-container #vc></ng-container>'
})
export class AppComponent {
  @ViewChild('vc', {read: ViewContainerRef}) vc;
  constructor(private compiler: Compiler, 
              private injector: Injector) {
  }
  load() {
    // register the modules that we already loaded so that no HTTP request is made
    // in my case, the modules are already available in my bundle (bundled by webpack)
    SystemJS.set('@angular/core', SystemJS.newModule(AngularCore));
    SystemJS.set('@angular/common', SystemJS.newModule(AngularCommon));
    // now, import the new module
    SystemJS.import('my-dynamic.component.js').then((module) => {
      this.compiler.compileModuleAndAllComponentsAsync(module.default)
            .then((compiled) => {
                let moduleRef = compiled.ngModuleFactory.create(this.injector);
                let factory = compiled.componentFactories[0];
                if (factory) {
                    let component = this.vc.createComponent(factory);
                    let instance = component.instance;
                }
            });
    });
  }
}
my-dynamic.component.ts
我的dynamic.component.ts
import { NgModule, Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Other } from './other';
@Component({
    selector: 'my-dynamic-component',
    template: '<h1>Dynamic component</h1><button (click)="LoadMore()">LoadMore</button>'
})    
export class MyDynamicComponent {
    LoadMore() {
        let other = new Other();
        other.hello();
    }
}
@NgModule({
    declarations: [MyDynamicComponent],
    imports: [CommonModule],
})
export default class MyDynamicModule {}
other.component.ts
other.component.ts
export class Other {
    hello() {
        console.log("hello");
    }
}
As you can see, we can tell SystemJS what modules already exist in our bundle. So we do not need to load them again (SystemJS.set). All other modules that we import in our my-dynamic-component(in this example other) will be requested from the server at runtime.
如您所见,我们可以告诉 SystemJS 我们的包中已经存在哪些模块。所以我们不需要再次加载它们 ( SystemJS.set)。我们在my-dynamic-component(在本例中other)导入的所有其他模块将在运行时从服务器请求。
回答by Lars Meijdam
I've used the https://github.com/kirjs/angular-dynamic-module-loadingsolution with Angular 6's library support to create an application I shared on Github. Due to company policy it needed to be taken offline. As soon as discussions are over regarding the example project source I will share it on Github!
我使用了https://github.com/kirjs/angular-dynamic-module-loading解决方案和 Angular 6 的库支持来创建我在 Github 上共享的应用程序。由于公司政策,它需要离线。一旦关于示例项目源的讨论结束,我将在 Github 上分享它!
UPDATE: repo can be found ; https://github.com/lmeijdam/angular-umd-dynamic-example
更新:可以找到回购;https://github.com/lmeijdam/angular-umd-dynamic-example
回答by Vitaliy Vostroknutov
Do it with angular 6 library and rollup do the trick. I've just experiment with it and i can share standalone angular AOT module with the main app without rebuild last.
用 angular 6 库来做,汇总就可以了。我刚刚尝试过它,我可以与主应用程序共享独立的 angular AOT 模块,而无需最后重建。
- In angular library set 
angularCompilerOptions.skipTemplateCodegento false and after build library you will get module factory. - After that build an umd module with rollup like this: 
rollup dist/plugin/esm2015/lib/plugin.module.ngfactory.js --file src/assets/plugin.module.umd.js --format umd --name plugin - Load text source umd bundle in main app and eval it with module context
 - Now you can access to ModuleFactory from exports object
 
- 在 angular 库中设置
angularCompilerOptions.skipTemplateCodegen为 false 并且在构建库之后您将获得模块工厂。 - 之后构建一个带有汇总的 umd 模块,如下所示: 
rollup dist/plugin/esm2015/lib/plugin.module.ngfactory.js --file src/assets/plugin.module.umd.js --format umd --name plugin - 在主应用程序中加载文本源 umd 包并使用模块上下文对其进行评估
 - 现在您可以从导出对象访问 ModuleFactory
 
Here https://github.com/iwnow/angular-plugin-exampleyou can find how develop plugin with standalone building and AOT
在这里https://github.com/iwnow/angular-plugin-example您可以找到如何使用独立构建和 AOT 开发插件
回答by Robin Ding
I have tested in Angular 6, below solution works for dynamically loading a module from an external package or an internal module.
我已经在 Angular 6 中进行了测试,以下解决方案适用于从外部包或内部模块动态加载模块。
1. If you want to dynamically load a module from a library project or a package:
1.如果要从库项目或包中动态加载模块:
I have a library project "admin" (or you can use a package) and an application project "app". In my "admin" library project, I have AdminModule and AdminRoutingModule. In my "app" project:
我有一个库项目“admin”(或者您可以使用包)和一个应用程序项目“app”。在我的“admin”库项目中,我有 AdminModule 和 AdminRoutingModule。在我的“应用程序”项目中:
a. Make change in tsconfig.app.json:
一种。在 tsconfig.app.json 中进行更改:
  "compilerOptions": {
    "module": "esNext",
  },
b. In app-routing.module.ts:
湾 在 app-routing.module.ts 中:
const routes: Routes = [
    {
        path: 'admin',
        loadChildren: async () => {
            const a = await import('admin')
            return a['AdminModule'];
        }
    },
    {
        path: '',
        redirectTo: '',
        pathMatch: 'full'
    }
];
@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule {
}
2. if you want to load a module from the same project.
2.如果你想从同一个项目中加载一个模块。
There are 4 different options:
有4种不同的选择:
a. In app-routing.module.ts:
一种。在 app-routing.module.ts 中:
const routes: Routes = [
    {
        path: 'example',
        /* Options 1: Use component */
        // component: ExampleComponent,  // Load router from component
        /* Options 2: Use Angular default lazy load syntax */
        loadChildren: './example/example.module#ExampleModule',  // lazy load router from module
        /* Options 3: Use Module */
        // loadChildren: () => ExampleModule, // load router from module
        /* Options 4: Use esNext, you need to change tsconfig.app.json */
        /*
        loadChildren: async () => {
            const a = await import('./example/example.module')
            return a['ExampleModule'];
        }
        */
    },
    {
        path: '',
        redirectTo: '',
        pathMatch: 'full'
    }
];
@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule {
}
``
回答by N.M.
I believe this is possible using SystemJS to load a UMD bundle if you build and run your main application using webpack. I used a solution that uses ng-packagr to build a UMD bundle of the dynamic plugin/addon module. This github demonstrates the procedure described: https://github.com/nmarra/dynamic-module-loading
我相信,如果您使用 webpack 构建和运行主应用程序,则可以使用 SystemJS 加载 UMD 包。我使用了一个使用 ng-packagr 来构建动态插件/插件模块的 UMD 包的解决方案。这个 github 演示了描述的过程:https: //github.com/nmarra/dynamic-module-loading
回答by Jaya Krishna
Yes, you can lazy load modules using by referring them as modules in the router. Here is an example https://github.com/start-angular/SB-Admin-BS4-Angular-6
是的,您可以通过将它们称为路由器中的模块来延迟加载模块。这是一个例子https://github.com/start-angular/SB-Admin-BS4-Angular-6
- First couple all the components that you are using into a single module
 - Now refer that module in the router and angular will lazy load your module into the view.
 
- 首先将您使用的所有组件耦合到一个模块中
 - 现在在路由器中引用该模块,angular 会将您的模块延迟加载到视图中。
 

