wake-up-neo.com

Laden Sie neue Module zur Laufzeit dynamisch mit Angular CLI & Angular 5

Derzeit arbeite ich an einem Projekt, das auf einem Client-Server gehostet wird. Für neue "Module" gibt es keine Neukompilierung beabsichtigt die gesamte Anwendung. Der Client möchte jedoch Aktualisieren der Router/Lazy geladenen Module zur Laufzeit. Ich habe verschiedene Dinge ausprobiert, aber ich kann nicht, dass es funktioniert. Ich habe mich gefragt, ob einer von euch weiß, was ich noch probieren kann oder was ich vermisst habe.

Eines ist mir aufgefallen, dass die meisten Ressourcen, die ich mithilfe von eckig cli ausprobiert habe, bei der Erstellung der Anwendung standardmäßig per Webpack in separate Blöcke gebündelt werden. Was logisch erscheint, da das Webpack-Code-Splitting verwendet wird. was aber, wenn das Modul zur Kompilierzeit noch nicht bekannt ist (aber ein kompiliertes Modul wird irgendwo auf einem Server gespeichert)? Die Bündelung funktioniert nicht, da das zu importierende Modul nicht gefunden wird. Durch die Verwendung von SystemJS werden UMD-Module immer dann geladen, wenn sie auf dem System gefunden werden, sie werden jedoch auch in einem separaten Block für Webpacks zusammengefasst.

Einige Ressourcen, die ich bereits ausprobiert habe;

Einige Codes, die ich bereits ausprobiert und implementiert habe, aber zu diesem Zeitpunkt nicht funktionierten;

Erweitern des Routers mit der normalen module.ts-Datei

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

Normaler SystemJS-Import des UMD-Pakets

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

    });
});

Importieren eines externen Moduls, das nicht mit dem Webpack arbeitet (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);
});

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

Versucht, ein mit Rollup erstelltes Modul zu laden

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

Beispiel mit SystemJsNgModuleLoader funktioniert nur, wenn das Modul im Routermodul der App bereits als "Lazy" -Route bereitgestellt wird (was bei der Erstellung mit Webpack zu einem Block wird)

Ich habe hier und da eine Menge Diskussionen zu diesem Thema auf StackOverflow gefunden, und die bereitgestellten Lösungen scheinen wirklich gut zu sein, wenn Module/Komponenten dynamisch geladen werden, wenn sie im Voraus bekannt sind. aber keiner passt zu unserem Anwendungsfall des Projekts. Bitte lassen Sie mich wissen, was ich noch ausprobieren oder eintauchen kann.

Vielen Dank!

EDIT: Ich habe gefunden; https://github.com/kirjs/angular-dynamic-module-loading und werde es versuchen.

UPDATE: Ich habe ein Repository mit einem Beispiel für das dynamische Laden von Modulen mit SystemJS (und mit Angular 6) erstellt. https://github.com/lmeijdam/angular-umd-dynamic-example

20
Lars Meijdam

Ich hatte das gleiche Problem. Soweit ich es bis jetzt verstanden habe: 

Webpack fügt alle Ressourcen in einem Paket zusammen und ersetzt alle System.import durch __webpack_require__. Wenn Sie also zur Laufzeit ein Modul dynamisch mit SystemJsNgModuleLoader laden möchten, sucht der Loader nach dem Modul im Bundle. Wenn das Modul nicht im Bundle vorhanden ist, erhalten Sie eine Fehlermeldung. Webpack fragt den Server nicht nach diesem Modul. Dies ist ein Problem für uns, da wir ein Modul laden möchten, das wir zur Zeit der Erstellung/Kompilierung nicht kennen. Was wir brauchen, ist ein Loader, der zur Laufzeit ein Modul für uns lädt (faul und dynamisch). In meinem Beispiel verwende ich SystemJS und Angular 6/CLI. 

  1. Installieren Sie SystemJS: npm install systemjs –save
  2. Fügen Sie es zu angle.json hinzu: "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");
    }
}

Wie Sie sehen, können wir SystemJS mitteilen, welche Module in unserem Bundle bereits vorhanden sind. Daher müssen wir sie nicht erneut laden (SystemJS.set). Alle anderen Module, die wir in unserem my-dynamic-component (in diesem Beispiel other) importieren, werden zur Laufzeit vom Server angefordert.

8
Michael

Ich habe die Lösung https://github.com/kirjs/angular-dynamic-module-loading mit Angular 6-Bibliotheksunterstützung verwendet, um eine auf Github freigegebene Anwendung zu erstellen. Aufgrund der Unternehmensrichtlinien musste es offline genommen werden. Sobald die Diskussion über die Quelle des Beispielprojekts abgeschlossen ist, werde ich sie auf Github veröffentlichen!

UPDATE: Repo kann gefunden werden; https://github.com/lmeijdam/angular-umd-dynamic-example

4
Lars Meijdam

Machen Sie es mit der eckigen 6-Bibliothek und rollen Sie den Trick. Ich habe gerade damit experimentiert und ich kann ein eigenständiges Winkel-AOT-Modul mit der Haupt-App gemeinsam nutzen, ohne zuletzt umbauen zu müssen.

  1. In der Winkelbibliothek setzen Sie angleCompilerOptions.skipTemplateCodegen auf false, und nach der Build-Bibliothek erhalten Sie die Modul-Factory.

  2. Erstellen Sie anschließend ein umd-Modul mit einem Rollup wie folgt: rollup Dist/plugin/esm2015/lib/plugin.module.ngfactory.js --file src/assets/plugin.module.umd.js --format umd - Name Plugin

  3. Laden Sie die Textquelle umd in die Hauptanwendung und bewerten Sie sie mit dem Modulkontext
  4. Jetzt können Sie vom Exportobjekt aus auf ModuleFactory zugreifen

Hier https://github.com/iwnow/angular-plugin-example erfahren Sie, wie Sie Plugins mit Standalone Building und AOT entwickeln 

Ich glaube, dass dies mit SystemJS möglich ist, um ein UMD-Paket zu laden, wenn Sie Ihre Hauptanwendung mit Webpack erstellen und ausführen. Ich habe eine Lösung verwendet, die ng-packagr verwendet, um ein UMD-Bundle des dynamischen Plugin/Addon-Moduls zu erstellen. Dieser Github veranschaulicht das beschriebene Verfahren: https://github.com/nmarra/dynamic-module-loading

2
N.M.

Ja, Sie können Lazy Load-Module verwenden, indem Sie sie als Module im Router bezeichnen. Hier ist ein Beispiel https://github.com/start-angular/SB-Admin-BS4-Angular-6

  1. Koppeln Sie zunächst alle Komponenten, die Sie verwenden, in ein einziges Modul
  2. Nun beziehen Sie sich auf dieses Modul im Router, und eckig wird das Modul in die Ansicht geladen.
0
Jaya Krishna

Ich habe in Angular 6 getestet, die folgende Lösung funktioniert für das dynamische Laden eines Moduls aus einem externen Paket oder einem internen Modul. 

1. Wenn Sie ein Modul dynamisch aus einem Bibliotheksprojekt oder einem Paket laden möchten:

Ich habe ein Bibliotheksprojekt "admin" (oder Sie können ein Paket verwenden) und ein Anwendungsprojekt "app" . In meinem Bibliotheksprojekt "admin" habe ich AdminModule und AdminRoutingModule. In meinem "App" -Projekt:

ein. Nehmen Sie Änderungen in tsconfig.app.json vor:

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

b. In 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. wenn Sie ein Modul aus demselben Projekt laden möchten.

Es gibt 4 verschiedene Optionen:

ein. In 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
Robin Ding