web-dev-qa-db-fra.com

Chargez de nouveaux modules de manière dynamique en cours d'exécution avec Angular CLI & Angular 5

Actuellement, je travaille sur un projet hébergé sur un serveur client. Pour les nouveaux 'modules', il y a aucune intention de recompiler toute l'application. Cela dit, le client veut mettre à jour les modules de routeur/chargés paresseux au moment de l'exécution. J'ai essayé plusieurs choses mais je ne peux pas le faire fonctionner. Je me demandais si l'un de vous savait ce que je pouvais encore essayer ou ce que j'avais raté.

Une chose que j’ai remarquée, la plupart des ressources que j’ai essayées, à l’aide de clular angular, sont regroupées par segment dans des blocs distincts par Webpack par défaut lors de la création de l’application. Ce qui semble logique dans la mesure où il utilise le fractionnement de code webpack. mais que se passe-t-il si le module n'est pas encore connu au moment de la compilation (mais qu'un module compilé est stocké quelque part sur un serveur)? Le regroupement ne fonctionne pas car il ne trouve pas le module à importer. Et utiliser SystemJS chargera des modules UMD à chaque fois qu’ils se trouvent sur le système, mais sont également regroupés dans un bloc séparé par Webpack.

Quelques ressources que j'ai déjà essayées;

J'ai déjà essayé et implémenté du code, mais je ne travaille pas pour le moment;

Extension du routeur avec le fichier normal module.ts

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

Importation SystemJS normale du paquet 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);

    });
});

Importer un module externe, ne fonctionnant pas avec Webpack

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

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

Essayé de charger un module fait avec 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();

Exemple avec SystemJsNgModuleLoader ne fonctionne que lorsque le module est déjà fourni en tant que route «paresseuse» dans le RouterModule de l'application (ce qui en fait un bloc lorsqu'il est construit avec Webpack).

J'ai trouvé beaucoup de discussions sur ce sujet sur StackOverflow ici et là et les solutions fournies semblent vraiment bonnes pour charger des modules/composants de manière dynamique si elles sont connues dès le départ. mais aucun ne convient à notre cas d'utilisation du projet. S'il vous plaît laissez-moi savoir ce que je peux encore essayer ou plonger dans.

Merci!

EDIT: j'ai trouvé; https://github.com/kirjs/angular-dynamic-module-loading et vous donnera l’essai.

UPDATE: J'ai créé un référentiel avec un exemple de chargement dynamique de modules à l'aide de SystemJS (et à l'aide de Angular 6); https://github.com/lmeijdam/angular-umd-dynamic-example

20
Lars Meijdam

Je faisais face au même problème. Autant que je sache jusqu'à présent: 

Webpack regroupe toutes les ressources et remplace tout System.import par __webpack_require__. Par conséquent, si vous souhaitez charger un module de manière dynamique lors de l'exécution à l'aide de SystemJsNgModuleLoader, le chargeur recherche le module dans l'ensemble. Si le module n'existe pas dans le bundle, vous obtiendrez une erreur. Webpack ne demandera pas ce module au serveur. Ceci est un problème pour nous, car nous voulons charger un module que nous ne connaissons pas au moment de la compilation/construction . Dans mon exemple, j'utilise SystemJS et Angular 6/CLI. 

  1. Installez SystemJS: npm install systemjs –save
  2. Ajoutez-le à 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");
    }
}

Comme vous pouvez le constater, nous pouvons dire à SystemJS quels modules existent déjà dans notre bundle. Nous n'avons donc pas besoin de les charger à nouveau (SystemJS.set). Tous les autres modules que nous importons dans notre my-dynamic-component (dans cet exemple other) seront demandés au serveur lors de l'exécution.

8
Michael

J'ai utilisé la solution https://github.com/kirjs/angular-dynamic-module-loading avec le support de bibliothèque d'Angular 6 pour créer une application que j'ai partagée sur Github. En raison de la politique de l'entreprise, il devait être mis hors ligne. Dès que les discussions sur l’exemple de la source du projet seront terminées, je le partagerai sur Github!

UPDATE: repo peut être trouvé; https://github.com/lmeijdam/angular-umd-dynamic-example

4
Lars Meijdam

Faites-le avec la bibliothèque angulaire 6 et le cumulatif faites l'affaire. Je viens d'expérimenter et je peux partager le module AOT angulaire autonome avec l'application principale sans reconstruire en dernier.

  1. Dans la bibliothèque angulaire, définissez angularCompilerOptions.skipTemplateCodegen sur false et, après la construction de la bibliothèque, vous obtiendrez une fabrique de modules.

  2. Après cela, construisez un module umd avec un rollup comme ceci: rollup Dist/plugin/esm2015/lib/plugin.module.ngfactory.js --file src/assets/plugin.module.umd.js --format umd - nom plugin

  3. Chargez le paquet umd source de texte dans l'application principale et évaluez-le avec le contexte du module
  4. Vous pouvez maintenant accéder à ModuleFactory à partir de l'objet exports

Ici https://github.com/iwnow/angular-plugin-example vous pouvez trouver comment développer un plugin avec un bâtiment autonome et AOT 

2

Je pense que cela est possible en utilisant SystemJS pour charger un bundle UMD si vous construisez et exécutez votre application principale à l'aide de webpack. J'ai utilisé une solution qui utilise ng-packagr pour construire un ensemble UMD du module plug-in/addon dynamique. Ce github illustre la procédure décrite: https://github.com/nmarra/dynamic-module-loading

2
N.M.

Oui, vous pouvez charger paresseux des modules en les référant comme des modules dans le routeur. Voici un exemple https://github.com/start-angular/SB-Admin-BS4-Angular-6

  1. Commencez par coupler tous les composants que vous utilisez dans un seul module
  2. Reportez-vous maintenant à ce module dans le routeur et angular chargera votre module paresseux dans la vue.
0
Jaya Krishna

J'ai testé dans Angular 6, la solution ci-dessous fonctionne pour le chargement dynamique d'un module à partir d'un package externe ou d'un module interne. 

1. Si vous souhaitez charger dynamiquement un module à partir d'un projet de bibliothèque ou d'un package:

J'ai un projet de bibliothèque "admin" (ou vous pouvez utiliser un package) et un projet d'application "app" . Dans mon projet de bibliothèque "admin", j'ai AdminModule et AdminRoutingModule. Dans mon projet "app":

une. Faire le changement dans tsconfig.app.json:

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

b. Dans 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. si vous souhaitez charger un module du même projet.

Il y a 4 options différentes:

une. Dans 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