62 votes

Charger dynamiquement de nouveaux modules au moment de l'exécution avec Angular CLI et Angular 5

Actuellement, je travaille sur un projet qui est hébergé sur un des clients du serveur. Pour les nouveaux "modules" il n'y a aucune intention de recompiler l'ensemble de l'application. Cela dit, le client veut mettre à jour le routeur/paresseux modules chargés dans le runtime. J'ai essayé plusieurs choses mais je ne peux pas le faire fonctionner. Je me demandais si l'un de vous sait ce que je pourrais toujours essayer ou de ce que j'ai raté.

Une chose que j'ai remarqué, la plupart des ressources que j'ai essayé, à l'aide angulaire de la cli, sont regroupés en deux morceaux par webpack par défaut lors de la construction de l'application. Ce qui semble logique, car il rend l'utilisation de la webpack code de fractionnement. mais que faire si le module n'est pas encore connu au moment de la compilation (mais la compilation d'un module est stocké quelque part sur un serveur)? Le regroupement ne fonctionne pas car il ne trouve pas le module d'importation. Et à l'Aide de SystemJS vous permettra de charger jusqu'UMD modules où qu'ils se trouvent sur le système, mais sont également regroupés dans un autre morceau par webpack.

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

Certains code, je l'ai déjà essayé et à mettre en œuvre, mais ne fonctionne pas à ce moment;

L'extension de routeur avec la normale du module.fichier ts

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

Normal SystemJS Importation de UMD bundle

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

    });
});

Importation de module externe, pas de travail avec webpack (autant que je sache)

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

Utilisation 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é le chargement d'un module avec le correctif cumulatif

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à prévu comme "paresseux" voie de la RouterModule de l'application (ce qui en fait un morceau construit avec webpack)

J'ai trouvé beaucoup de discussion sur ce sujet sur StackOverflow ici et là et les solutions apportées semblent vraiment bon de charger les modules/composants dynamiquement si elle est connue d'avance. mais il n'est pas convenable pour notre cas d'utilisation du projet. S'il vous plaît laissez-moi savoir ce que je peux toujours essayer ou de plongée dans.

Merci!

EDIT: j'ai trouvé; https://github.com/kirjs/angular-dynamic-module-loading et de donner à ceci un essai.

Mise à JOUR: j'ai créé une archive avec un exemple de chargement des modules dynamiquement à l'aide de SystemJS (et en utilisant Angulaire 6); https://github.com/lmeijdam/angular-umd-dynamic-example

17voto

Michael Points 169

J'ai été confrontée au même problème. Autant je comprends que jusqu'à maintenant:

Webpack met toutes les ressources dans un bundle et remplace tous System.import avec __webpack_require__. Par conséquent, si vous souhaitez charger un module dynamiquement lors de l'exécution à l'aide de SystemJsNgModuleLoader, le chargeur de recherche pour le module dans le bundle. Si le module n'existe pas dans le bundle, vous obtiendrez une erreur. Webpack ne va pas demander au serveur pour ce module. C'est un problème pour nous, car nous voulons charger un module que nous ne savons pas à construire/moment de la compilation. Nous avons besoin d'un chargeur qui vous permettra de charger un module pour nous au moment de l'exécution (paresseux et dynamique). Dans mon exemple, je suis en utilisant SystemJS et Angulaire 6 / CLI.

  1. Installer SystemJS: npm install systemjs –enregistrer
  2. Ajouter angulaire.json: "scripts": [ "node_modules/systemjs/dist/system.src.js"]

app.composante.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;
                }
            });
    });
  }
}

mon-dynamique.composante.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 {}

d'autres.composante.ts

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

Comme vous pouvez le voir, nous pouvons dire SystemJS ce que les modules existent déjà dans notre bundle. Donc, nous n'avons pas besoin de les charger à nouveau (SystemJS.set). Tous les autres modules que nous importons dans notre my-dynamic-component (dans cet exemple, other) sera demandée à partir du serveur au moment de l'exécution.

6voto

Lars Meijdam Points 456

J'ai utilisé le https://github.com/kirjs/angular-dynamic-module-loading solution Angulaire 6 bibliothèque du soutien à la création d'une application que j'ai partagé sur Github. En raison de la politique de l'entreprise, il doit être pris en mode hors connexion. Dès que des discussions sont en cours à propos de l'exemple de projet source, je vais le partager sur Github!

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

4voto

Faire angulaire 6 bibliothèque et cumulatif de faire le tour. Je viens de le tester et je peux partager autonome angulaire AOT module avec l'application principale sans reconstruction dernier.

  1. Dans angulaire de la bibliothèque a établi angularCompilerOptions.skipTemplateCodegen de faux et après la construction de la bibliothèque, vous obtiendrez module d'usine.
  2. Après que construire un umd module with rollup comme ceci: rollup dist/plugin/esm2015/lib/plugin.module.ngfactory.js --file src/assets/plugin.module.umd.js --format umd --name plugin
  3. Charger le texte source umd bundle dans l'application principale et eval avec module de contexte
  4. Maintenant, vous pouvez accéder à ModuleFactory des exportations de l'objet

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

4voto

Robin Ding Points 381

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

1. Si vous voulez charger dynamiquement un module à partir d'un projet de bibliothèque ou d'un forfait.

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

un. Faire des changements dans le tsconfig.app.json:

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

b. En application de routage.le 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 à partir d'un même projet.

Il y a 4 options différentes:

un. En application de routage.le 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 {
}
``

2voto

N.M. Points 21

Je crois 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 bundle UMD du module plugin / addon dynamique. Ce github illustre la procédure décrite: https://github.com/nmarra/dynamic-module-loading

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X