web-dev-qa-db-ja.com

Angular CLIおよびAngular 5を使用して、実行時に新しいモジュールを動的にロードします

現在、クライアントサーバーでホストされているプロジェクトに取り組んでいます。新しい「モジュール」の場合、アプリケーション全体を再コンパイルする意図はありません。つまり、クライアントは、実行時にルーター/遅延ロードされたモジュールを更新したいとします。私はいくつかのことを試してみましたが、動作させることができません。まだ試してみたいことや見逃したことを知っている人がいるかどうか疑問に思っていました。

気づいたのは、angular cliを使用して試してみたリソースのほとんどが、アプリケーションのビルド時にデフォルトでwebpackによって個別のチャンクにバンドルされていることです。これは、webpackコード分割を利用するため、論理的に思えます。しかし、コンパイル時にモジュールがまだ知られていない場合はどうなりますか(しかし、コンパイルされたモジュールはサーバー上のどこかに保存されます)?インポートするモジュールが見つからないため、バンドルは機能しません。また、SystemJSを使用すると、システム上でUMDモジュールが見つかるたびにロードされますが、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

35
Lars Meijdam

私は同じ問題に直面していました。私の知る限り、これまでのところ:

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

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)にインポートする他のすべてのモジュールは、実行時にサーバーから要求されます。

10
Michael

https://github.com/kirjs/angular-dynamic-module-loading ソリューションとAngular 6のライブラリサポートを使用して、Githubで共有したアプリケーションを作成しました。会社のポリシーにより、オフラインにする必要がありました。サンプルプロジェクトソースに関する議論が終わったら、すぐにGithubで共有します!

更新:リポジトリが見つかりました。 https://github.com/lmeijdam/angular-umd-dynamic-example

4
Lars Meijdam

angular 6ライブラリを使用して実行し、ロールアップがトリックを実行します。私はそれを試してみましたが、最後に再構築せずに、スタンドアロンのangular AOTモジュールをメインアプリと共有できます。

  1. angularライブラリでangularCompilerOptions.skipTemplateCodegenをfalseに設定し、ライブラリのビルド後にモジュールファクトリを取得します。
  2. その後、次のようなロールアップでumdモジュールをビルドします:rollup dist/plugin/esm2015/lib/plugin.module.ngfactory.js --file src/assets/plugin.module.umd.js --format umd --name plugin
  3. メインアプリにテキストソースumdバンドルを読み込み、モジュールコンテキストで評価する
  4. これで、exportsオブジェクトからModuleFactoryにアクセスできます

ここ https://github.com/iwnow/angular-plugin-example スタンドアロンのビルドとAOTでプラグインを開発する方法を見つけることができます

Webpackを使用してメインアプリケーションをビルドおよび実行する場合、これはSystemJSを使用してUMDバンドルをロードできると考えています。 ng-packagrを使用するソリューションを使用して、動的プラグイン/アドオンモジュールのUMDバンドルを構築しました。このgithubは、説明されている手順を示しています。 https://github.com/nmarra/dynamic-module-loading

2
N.M.

Angular 6でテストしましたが、以下のソリューションは外部パッケージまたは内部モジュールからモジュールを動的にロードするために機能します。

1。ライブラリプロジェクトまたはパッケージからモジュールを動的にロードする場合:

ライブラリプロジェクト "admin"(またはパッケージを使用できます)とアプリケーションプロジェクト "app"があります。 「admin」ライブラリプロジェクトには、AdminModuleとAdminRoutingModuleがあります。私の「アプリ」プロジェクトで:

a。 tsconfig.app.jsonに変更を加えます。

  "compilerOptions": {
    "module": "esNext",
  },

b。 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。同じプロジェクトからモジュールをロードする場合。

4つの異なるオプションがあります。

a。 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 {
}
``

2
Robin Ding

はい、ルーターでモジュールとして参照することで、モジュールを遅延ロードできます。以下に例を示します https://github.com/start-angular/SB-Admin-BS4-Angular-6

  1. 最初に、使用しているすべてのコンポーネントを単一のモジュールに結合します
  2. ルーターでそのモジュールを参照すると、angularがモジュールをビューに遅延ロードします。
0
Jaya Krishna