首页 > 解决方案 > 使用 Angular CLI 和 Angular 5 在运行时动态加载新模块

问题描述

目前我正在开发一个托管在客户端服务器上的项目。对于新的“模块”,无意重新编译整个应用程序。也就是说,客户端希望在运行时更新路由器/延​​迟加载的模块。我已经尝试了几件事,但我无法让它工作。我想知道你们中是否有人知道我还能尝试什么或我错过了什么。

我注意到的一件事是,在构建应用程序时,默认情况下,我使用 angular cli 尝试的大多数资源都被 webpack 捆绑成单独的块。这似乎是合乎逻辑的,因为它利用了 webpack 代码拆分。但是如果模块在编译时还不知道(但是编译的模块存储在服务器上的某个地方)怎么办?捆绑不起作用,因为它找不到要导入的模块。并且只要在系统上找到 UMD 模块,使用 SystemJS 就会加载,但也会被 webpack 捆绑在一个单独的块中。

我已经尝试过的一些资源;

我已经尝试并实现了一些代码,但目前无法正常工作;

使用普通 module.ts 文件扩展路由器

  this.router.config.push({
    path: "external",
    loadChildren: () =>
      System.import("./module/external.module").then(
        module => module["ExternalModule"],
        () => {
          throw { loadChunkError: true };
        }
      )
  });

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);
    });
});

导入外部模块,不适用于 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);
});

使用 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);
  });

尝试加载使用汇总制作的模块

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();

SystemJsNgModuleLoader 的示例仅在模块已在应用程序的 RouterModule 中作为“惰性”路由提供时才有效(使用 webpack 构建时将其转换为块)

我在这里和那里在 StackOverflow 上发现了很多关于这个主题的讨论,并且如果事先知道的话,提供的解决方案似乎非常适合动态加载模块/组件。但没有一个适合我们的项目用例。请让我知道我仍然可以尝试或潜入什么。

谢谢!

编辑:我发现;https://github.com/kirjs/angular-dynamic-module-loading并尝试一下。

更新:我创建了一个存储库,其中包含一个使用 SystemJS(并使用 Angular 6)动态加载模块的示例;https://github.com/lmeijdam/angular-umd-dynamic-example

标签: javascriptangularwebpackangular-cliangular5

解决方案


我面临着同样的问题。据我了解到现在:

Webpack 将所有资源放在一个包中,并System.import__webpack_require__. 因此,如果您想在运行时使用 SystemJsNgModuleLoader 动态加载模块,加载器将在包中搜索模块。如果包中不存在该模块,您将收到错误消息。Webpack 不会向服务器询问该模块。这对我们来说是个问题,因为我们想加载一个在构建/编译时我们不知道的模块。我们需要的是在运行时(惰性和动态)为我们加载模块的加载器。在我的示例中,我使用的是 SystemJS 和 Angular 6 / CLI。

  1. 安装 SystemJS: npm install systemjs –save
  2. 将其添加到 angular.json: "scripts": [ "node_modules/systemjs/dist/system.src.js"]

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;
                }
            });
    });
  }
}

我的-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 {}

其他.component.ts

export class Other {
    hello() {
        console.log("hello");
    }
}

如您所见,我们可以告诉 SystemJS 哪些模块已经存在于我们的包中。所以我们不需要再次加载它们(SystemJS.set)。我们在我们的my-dynamic-component(在这个例子中other)中导入的所有其他模块将在运行时从服务器请求。


推荐阅读