Динамически загружайте новые модули во время выполнения с Angular CLI & Angular 5
В настоящее время я работаю над проектом, который размещается на клиентском сервере. Для новых "модулей" нет намерения перекомпилировать все приложение. Тем не менее, клиент хочет обновить маршрутизатор / лениво загруженные модули во время выполнения. Я перепробовал несколько вещей, но не могу заставить его работать. Мне было интересно, знает ли кто-нибудь из вас, что я еще могу попробовать или что я пропустил.
Я заметил одну вещь: большинство ресурсов, которые я пробовал, используя angular cli, по умолчанию объединяются в отдельные блоки при создании приложения. Что кажется логичным, поскольку использует разделение кода веб-пакета. но что, если модуль еще не известен во время компиляции (но скомпилированный модуль хранится где-то на сервере)? Связывание не работает, потому что он не может найти модуль для импорта. А использование SystemJS будет загружать модули UMD, когда бы они ни находились в системе, но они также объединены в отдельный блок через веб-пакет.
Некоторые ресурсы я уже пробовал;
- динамический удаленный компонент-загрузчик
- Модуль заряжания
- Загрузка модулей с другого сервера во время выполнения
- Как загрузить динамические внешние компоненты в приложение 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
- Несколько других, связанных с этой темой.
Некоторый код, который я уже попробовал и реализовал, но в настоящее время не работает;
Расширение роутера обычным файлом 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 приложения (который превращает его в чанк при сборке с веб-пакетом)
Я нашел много дискуссий по этой теме о Stackru здесь и там, и при условии, что решения кажутся действительно хорошими для динамической загрузки модулей / компонентов, если они известны заранее. но ни один не подходит для нашего варианта использования проекта. Пожалуйста, дайте мне знать, что я еще могу попробовать или погрузиться в.
Спасибо!
РЕДАКТИРОВАТЬ: я нашел; https://github.com/kirjs/angular-dynamic-module-loading и попробую.
ОБНОВЛЕНИЕ: Я создал хранилище с примером динамической загрузки модулей с использованием SystemJS (и с использованием Angular 6); https://github.com/lmeijdam/angular-umd-dynamic-example
5 ответов
Я столкнулся с той же проблемой. Насколько я понимаю до сих пор
Webpack помещает все ресурсы в пакет и заменяет все System.import
с __webpack_require__
, Поэтому, если вы хотите динамически загрузить модуль во время выполнения с помощью SystemJsNgModuleLoader, загрузчик выполнит поиск модуля в комплекте. Если модуль не существует в комплекте, вы получите ошибку. Webpack не собирается запрашивать у сервера этот модуль. Это проблема для нас, так как мы хотим загрузить модуль, который мы не знаем во время сборки / компиляции. Нам нужен загрузчик, который загрузит модуль для нас во время выполнения (ленивый и динамический). В моем примере я использую SystemJS и Angular 6 / CLI.
- Установить SystemJS: npm установить systemjs –save
- Добавьте его в 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
) будет запрошен с сервера во время выполнения.
Я использовал решение https://github.com/kirjs/angular-dynamic-module-loading с поддержкой библиотек Angular 6 для создания приложения, которым я поделился на Github. Из-за политики компании ее нужно было отключить. Как только закончится обсуждение исходного проекта, я поделюсь им на Github!
ОБНОВЛЕНИЕ: репо можно найти; https://github.com/lmeijdam/angular-umd-dynamic-example
Сделайте это с угловой библиотекой 6 и сделайте свое дело. Я только что поэкспериментировал с этим, и я могу поделиться автономным угловым модулем AOT с основным приложением без перестройки последней.
В угловой библиотеке установите для angularCompilerOptions.skipTemplateCodegen значение false и после сборки библиотеки вы получите фабрику модулей.
После этого создайте модуль umd с помощью накопительного пакета следующим образом: rollup dist/plugin/esm2015/lib/plugin.module.ngfactory.js --file src/assets/plugin.module.umd.js --format umd --name плагин
- Загрузите исходный текстовый пакет umd в основное приложение и оцените его с помощью контекста модуля.
- Теперь вы можете получить доступ к ModuleFactory из объекта экспорта
Здесь https://github.com/iwnow/angular-plugin-example вы можете найти, как разработать плагин с автономной сборкой и AOT
Я протестировал в 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 {
}
``
Я считаю, что это возможно с помощью SystemJS для загрузки UMD-пакета, если вы создаете и запускаете свое основное приложение с помощью веб-пакета. Я использовал решение, использующее ng-packagr для построения UMD-пакета модуля динамического плагина / дополнения. Этот github демонстрирует описанную процедуру: https://github.com/nmarra/dynamic-module-loading
Да, вы можете лениво загружать модули, используя их как модули в маршрутизаторе. Вот пример https://github.com/start-angular/SB-Admin-BS4-Angular-6
- Сначала объедините все компоненты, которые вы используете, в один модуль.
- Теперь обратитесь к этому модулю в маршрутизаторе, и angular будет лениво загружать ваш модуль в поле зрения.
Не работает
System.import('http://lab.azaas.com:52048/my-component-library.umd.js').then(module => {
console.log(module);
});
за работой
System.import('./../../assets/umds/my-component-library.umd.js').then(module => {
console.log(module);
});