使用 Angular CLI & 在运行时动态加载新模块角 5 [英] Load new modules dynamically in run-time with Angular CLI & Angular 5

查看:27
本文介绍了使用 Angular CLI & 在运行时动态加载新模块角 5的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Currently I'm working on a project which is being hosted on a clients server. For new 'modules' there is no intention to recompile the 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.

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

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.

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

Some resources I already tried;

我已经尝试并实现了一些代码,但目前无法运行;

Some code I already tried and implement, but not working at this time;

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

Extending router with normal module.ts file

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

UMD 包的普通 SystemJS 导入

Normal SystemJS Import of UMD bundle

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)

Import external module, not working with 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

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

尝试加载一个用 rollup 制作的模块

Tried loading a module made with 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();

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

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)

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

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.

谢谢!

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

I've found; https://github.com/kirjs/angular-dynamic-module-loading and will give this a try.

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

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

推荐答案

我遇到了同样的问题.据我目前的理解:

I was facing the same problem. As far as I understand it until now:

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

Webpack puts all resources in a bundle and replaces all System.import with __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.

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

my-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

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

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

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.

这篇关于使用 Angular CLI &amp; 在运行时动态加载新模块角 5的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆