Динамически загружайте новые модули во время выполнения с Angular CLI & Angular 5 - PullRequest
0 голосов
/ 03 мая 2018

В настоящее время я работаю над проектом, который размещается на клиентском сервере. Для новых «модулей» нет намерения перекомпилировать всего приложения. Тем не менее, клиент хочет обновить маршрутизатор / лениво загруженные модули во время выполнения . Я перепробовал несколько вещей, но не могу заставить его работать. Мне было интересно, знает ли кто-нибудь из вас, что я еще могу попробовать или что я пропустил.

Одна вещь, которую я заметил, большинство ресурсов, которые я пробовал, используя angular cli, по умолчанию при сборке приложения объединяются в отдельные куски с помощью веб-пакета. Что кажется логичным, поскольку использует разделение кода веб-пакета. но что, если модуль еще не известен во время компиляции (но скомпилированный модуль хранится где-то на сервере)? Связывание не работает, потому что он не может найти модуль для импорта. А использование SystemJS будет загружать модули UMD всякий раз, когда они обнаруживаются в системе, но также объединяются в отдельный блок с помощью веб-пакета.

Некоторые ресурсы, которые я уже пробовал;

Некоторый код, который я уже попробовал и реализовал, но в настоящее время не работает;

Расширение роутера обычным файлом module.ts

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

Обычный SystemJS Импорт пакета UMD

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

    });
});

Импорт внешнего модуля, не работающего с веб-пакетом (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 приложения (который превращает его в чанк при сборке с веб-пакетом)

Я нашел много обсуждений по этой теме в StackOverflow тут и там, и при условии, что решения кажутся действительно хорошими для динамической загрузки модулей / компонентов, если они известны заранее. но ни один не подходит для нашего варианта использования проекта. Пожалуйста, дайте мне знать, что я еще могу попробовать или погрузиться.

Спасибо!

РЕДАКТИРОВАТЬ: я нашел; https://github.com/kirjs/angular-dynamic-module-loading и попробую.

ОБНОВЛЕНИЕ: Я создал репозиторий с примером динамической загрузки модулей с использованием SystemJS (и с использованием Angular 6); https://github.com/lmeijdam/angular-umd-dynamic-example

Ответы [ 6 ]

0 голосов
/ 25 февраля 2019

Я тестировал в Angular 6, ниже решение работает для динамической загрузки модуля из внешнего пакета или внутреннего модуля.

1. Если вы хотите динамически загрузить модуль из библиотечного проекта или пакета:

У меня есть проект библиотеки "admin" (или вы можете использовать пакет) и проект приложения "app". В моем проекте библиотеки "admin" у меня есть AdminModule и AdminRoutingModule. В моем проекте "app":

а. Внесите изменения в tsconfig.app.json:

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

б. В 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 варианта:

а. В 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 {
}
``

0 голосов
/ 02 октября 2018

Сделайте это с угловой библиотекой 6 и сделайте свое дело. Я только что поэкспериментировал с этим, и я могу поделиться автономным угловым модулем AOT с основным приложением без последней перестройки.

  1. В угловой библиотеке установите 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. Теперь вы можете получить доступ к ModuleFactory из объекта экспорта

Здесь https://github.com/iwnow/angular-plugin-example вы можете найти, как разработать плагин с автономной сборкой и AOT

0 голосов
/ 06 июня 2018

Я полагаю, что это возможно при использовании SystemJS для загрузки пакета UMD, если вы создаете и запускаете основное приложение с помощью веб-пакета. Я использовал решение, использующее ng-packagr для построения UMD-пакета модуля динамического плагина / дополнения. Этот github демонстрирует описанную процедуру: https://github.com/nmarra/dynamic-module-loading

0 голосов
/ 17 мая 2018

Я столкнулся с той же проблемой. Насколько я понимаю до сих пор:

Webpack помещает все ресурсы в пакет и заменяет все System.import на __webpack_require__. Поэтому, если вы хотите динамически загрузить модуль во время выполнения с помощью SystemJsNgModuleLoader, загрузчик будет искать модуль в комплекте. Если модуль не существует в комплекте, вы получите ошибку. Webpack не собирается запрашивать у сервера этот модуль. Это проблема для нас, так как мы хотим загрузить модуль, который мы не знаем во время сборки / компиляции. Нам нужен загрузчик, который загрузит модуль для нас во время выполнения (ленивый и динамический). В моем примере я использую SystemJS и Angular 6 / CLI.

  1. Установить SystemJS: npm установить 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 {}

other.component.ts

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

Как видите, мы можем сказать SystemJS, какие модули уже существуют в нашем комплекте. Поэтому нам не нужно загружать их снова (SystemJS.set). Все остальные модули, которые мы импортируем в наш my-dynamic-component (в этом примере other), будут запрашиваться с сервера во время выполнения.

0 голосов
/ 17 мая 2018

Да, вы можете лениво загружать модули, используя их как модули в маршрутизаторе. Вот пример https://github.com/start-angular/SB-Admin-BS4-Angular-6

  1. Сначала объедините все компоненты, которые вы используете, в один модуль
  2. Теперь обратите внимание, что модуль в роутере и angular будет лениво загружать ваш модуль в представление.
0 голосов
/ 17 мая 2018

Я использовал решение https://github.com/kirjs/angular-dynamic-module-loading с поддержкой библиотеки Angular 6 для создания приложения, которым я поделился на Github. Из-за политики компании ее нужно было отключить. Как только закончится обсуждение исходного проекта, я поделюсь им с Github!

ОБНОВЛЕНИЕ: репо можно найти; https://github.com/lmeijdam/angular-umd-dynamic-example

...