Angular: Les composants d'entrée d'un NgModule chargé paresseux ne sont pas disponibles en dehors du module

Créé le 6 févr. 2017  ·  122Commentaires  ·  Source: angular/angular

Je soumets un ... (cochez-en un avec "x")

[ ] bug report => search github for a similar issue or PR before submitting
[X] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Numéro de suivi pour # 12275 comme demandé par @DzmitryShylovich

Comportement actuel
entryComponents d'un NgModule chargé paresseux ne peut pas être rendu en utilisant ComponentFactoryResolver . Message d'erreur : No component factory found for {{entryComponent}}

Comportement prévisible
entryComponents devrait être disponible comme si le module était importé

Reproduction minimale du problème avec instructions
http://plnkr.co/edit/9euisYeSNbEFrfzOhhzf?p=preview

J'ai créé une configuration simple, similaire à celle que nous utilisons en interne. Le composant Main fournit une méthode pour rendre un Type . EntryComponent est déclaré comme entryComponent dans Page1Module . Cependant, après avoir chargé Page1Module , lors de la tentative de rendu EntryComponent via ComponentFactoryResolver , No component factory found for EntryComponent est lancé.

Quelle est la motivation / le cas d'utilisation pour changer le comportement ?
Rendre entryComponents des modules enfants au niveau racine. Les cas d'utilisation de cette approche sont

  • Modaux qui doivent être rendus au-dessus de tout
  • Avis
  • etc.

Veuillez nous parler de votre environnement :
Nous utilisons actuellement Angular 2.1.1 mais cela affecte également la dernière version d'Angular (2.4.6) (voir plnkr).

  • Langage : TypeScript ^2.0.0
core feature

Commentaire le plus utile

Ok, dans ce cas, je transforme ce problème en une demande de fonctionnalité.

IMO, ces cas d'utilisation doivent être gérés par Angular lui-même plutôt que de déplacer manuellement les éléments le long du DOM.

Tous les 122 commentaires

Après quelques recherches, j'ai compris que dans votre travail, tel que conçu.
Les composants d'entrée suivent les mêmes règles que les fournisseurs dans les modules à chargement différé. https://angular.io/docs/ts/latest/cookbook/ngmodule-faq.html#! #q-lazy-loaded-module-provider-visibility
Ils ne sont visibles qu'à l'intérieur du module chargé paresseux.

Ok, si c'est le comportement prévu, comment des tâches spécifiques comme les cas d'utilisation ci-dessus peuvent-elles être accomplies ?

Je ne pense pas que faire sauter le module principal soit une bonne solution.

Si vous souhaitez utiliser un composant d'entrée dans MainComponent , vous devez le fournir dans AppModule

Donc, votre solution est en effet de tout déplacer dans le module principal ?

IMO cela casse totalement le concept de module d'Angular qui devrait permettre de regrouper des blocs de fonctionnalités en un seul module.

Cela signifie essentiellement que des choses comme les modaux sont impossibles à utiliser avec une architecture de chargement paresseux.

Vous devez utiliser une API similaire au matériau https://material.angular.io/components/component/dialog
Alors ça devrait marcher

Wow, donc votre opinion est que nous devrions plutôt déplacer le contenu DOM, comme le fait Angular Material. Voir ici : https://github.com/angular/material2/blob/master/src/lib/core/portal/dom-portal-host.ts#L30 -L55

Si c'est vraiment la meilleure pratique pour gérer cela dans Angular, nous pouvons essentiellement vider Angular et recommencer à utiliser JQuery.

Désolé, mais je ne peux pas prendre cette approche au sérieux.

Je ne sais pas quelle est la meilleure approche pour les modaux et le chargement paresseux, mais de toute façon dans votre plunkr cela fonctionne comme prévu et il n'y a pas de bogue angulaire ici.
Pour discuter des modaux, il est préférable d'utiliser des canaux de support tels que gitter et stackoverflow

Ok, dans ce cas, je transforme ce problème en une demande de fonctionnalité.

IMO, ces cas d'utilisation doivent être gérés par Angular lui-même plutôt que de déplacer manuellement les éléments le long du DOM.

Face au même problème aujourd'hui - le composant pour modal défini dans le module chargé paresseux n'est pas géré par le service modal de l'application (il ne peut pas le trouver malgré l'utilisation entryComponents ). Obligé de le déplacer vers le bundle principal qui rompt la structure et la logique d'utilisation des modules paresseux :confused:

Même problème là ... Impossible d'utiliser un entryComponents dans un module lazyLoaded sans casser la logique des modules paresseux : Mon module LazyLoaded dépend d'un entryComponent déclaré dans mon AppModule :(

J'ai rencontré ce même problème, mais j'ai trouvé une solution après avoir parcouru le code source modal ng-bootstrap. Essentiellement, le problème (d'après ce que j'ai compris, corrigez-moi si je me trompe) est que vos modules paresseux ne sont pas inclus dans l'injecteur initial lorsque votre application est amorcée et que l'injecteur initial ne peut pas être modifié une fois qu'il est défini. C'est pourquoi vous obtenez l'erreur dans l'OP. Lorsqu'un de vos modules paresseux est chargé, il obtient son propre injecteur avec des références à tout ce que vous déclarez ou fournissez dans votre module paresseux. Cela étant dit, tant que l'endroit où vous créez votre composant dynamique fait référence aux bons injector et componentFactoryResolver , vous pouvez en effet référencer un entryComponent en dehors du module chargé paresseux.

Donc, ce que j'ai fait, c'est créer un service singleton pour stocker les injector et componentFactoryResolver du composant dynamique que je veux créer. Ce service doit être en dehors de vos modules paresseux. Ensuite, chaque fois que je crée dynamiquement un composant, je peux appeler ce service pour obtenir ce dont j'ai besoin.

Le code ci-dessous sera là où vous créez votre composant dynamique en dehors de votre module paresseux

@ViewChild('parent', {read: ViewContainerRef}) parent: ViewContainerRef;
  private componentRef: ComponentRef<any>;
...
const childComponent = this.componentFactoryResolver.resolveComponentFactory(yourComponentRef);
this.refInjector = ReflectiveInjector.resolveAndCreate([{provide: yourComponentRef, useValue: yourComponentRef}], this.injector);
this.componentRef = this.parent.createComponent(childComponent, 0, this.refInjector);

```html

Then in your parent component for your entryComponent you'll inject `injector` and `componentFactoryResolver` and set their values in the shared service:
```js
constructor(private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector) {}

Faites-moi savoir si cela n'a aucun sens et je peux élaborer davantage. Modifier j'ai oublié de mentionner que j'utilise Angular 2.4.9.

Merci @jmcclanahan. Pouvez-vous nous en dire un peu plus sur ce à quoi ressemble ce service et comment il est mis en place ? Je suis aux prises avec un problème similaire, en essayant de charger dynamiquement un composant d'un module chargé paresseux dans un composant d'un autre module chargé paresseux et même si (je pense) j'ai ajouté les composants d'entrée au bon endroit, on me dit que je pas. Merci

@gcorgnet - Bien sûr. Donc, mon cas d'utilisation était, j'ai une barre d'outils commune sur chaque page de mon application, mais dans la barre d'outils, je voulais ajouter des boutons et des outils pour des fonctionnalités supplémentaires en fonction de la page sur laquelle je me trouvais. Pour ce faire, j'ai créé un composant pour contenir ces boutons/outils et leur logique et je voulais garder ces composants à l'intérieur du module auquel ils sont associés. Ainsi, pratiquement rien en dehors de mon module ne sait quoi que ce soit sur ces fonctions spécifiques de la barre d'outils tout en pouvant les afficher sur la barre d'outils commune. Voici ma solution de travail qui, je l'espère, vous aidera:

Tout ce que je fais avec le service de barre d'outils est de créer un observable, de sorte que votre composant paresseux peut transmettre ses références componentFactoryResolver et injector au composant commun de la barre d'outils qui écoute le receiveContext Événement.

barre d'outils.service.ts

@Injectable()
export class ToolbarService {
    contextReceivedSource = new Subject<any>();
    contextReceived$ = this.contextReceivedSource.asObservable();

    receiveContext(componentFactoryResolver: ComponentFactoryResolver, injector: Injector) {
        this.contextReceivedSource.next({ resolver: componentFactoryResolver, injector: injector });
    }
}

Dans votre composant chargé paresseux, vous voudrez injecter componentFactoryResolver et injector et déclencher un événement dans le service de la barre d'outils.

composant de barre d'outils chargé paresseusement

...
constructor(private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector) {}
...
ngOnInit() {
  this.toolbarService.receiveContext(this.componentFactoryResolver, this.injector);
}
...

Enfin, dans mon composant principal de la barre d'outils, je m'abonne à l'observable dans le service de la barre d'outils afin qu'à chaque fois qu'un événement est déclenché, il essaie d'injecter et de créer le composant paresseux dont j'ai besoin. Il est également important de détruire les componentRef que vous créez, sinon vous vous retrouverez avec des fuites de mémoire.

...
@ViewChild('toolbarTarget', {read: ViewContainerRef}) toolbarTarget: ViewContainerRef;
component: Type<Component>;
refInjector: ReflectiveInjector;
resolverSub: Subscription;
refInjector: ReflectiveInjector;
componentFactoryResolver: ComponentFactoryResolver;
injector: Injector;

ngOnInit() {
  this.resolverSub = this.toolbarService.contextReceived$.subscribe(resolver => {
      this.componentFactoryResolver = resolver.resolver;
      this.injector = resolver.injector;
    });
}

updateToolbar(data: any) {
    this.clearComponent();
    this.component = data['toolbar'];
    if (this.component) {
      const childComponent = this.componentFactoryResolver.resolveComponentFactory(this.component);
      this.refInjector = ReflectiveInjector
          .resolveAndCreate([{provide: this.component, useValue: this.component}], this.injector);
      this.componentRef = this.toolbarTarget.createComponent(childComponent, 0, this.refInjector);
    }
  }

  clearComponent() {
    this.toolbarTarget.clear();
    if (this.componentRef) { this.componentRef.destroy(); };
  }

  ngOnDestroy() {
    this.resolverSub.unsubscribe();
    this.clearComponent();
  }

Votre composant créé dynamiquement sera alors placé là où vous mettez le #toolbarTarget correspondant

<div #toolbarTarget></div>

En passant, j'utilise cette ligne this.component = data['toolbar']; dans le composant de la barre d'outils commune pour obtenir la référence du composant chargé paresseux à partir de la route. Si vous voulez voir le code complet pour cela, je peux le poster, mais cela sort du cadre de cette discussion.

{ path: '', component: yourComponent, data: { toolbar: yourToolbarComponentToInject } }

Dis moi si tu as d'autres questions!

@jmcclanahan J'ai une question (probablement stupide) à propos de votre code, lorsque vous mentionnez le "composant de barre d'outils chargé paresseux", vous mettez le code d'appel de service dans la section ngOnInit, mais comment parvenez-vous à l'initialiser (le composant) sans avoir à l'ajouter au code HTML et à le cacher plus tard dans le DOM ? Étant donné que ce composant ne sera utilisé que dans la barre supérieure, je ne le charge nulle part ailleurs auparavant.

@Dunos - J'ai réalisé que j'avais complètement omis cette partie de mon exemple ci-dessus, désolé pour ça! Vous voudrez ajouter le composant dynamique en tant que entryComponent dans le module chargé paresseux auquel il est associé.

Ensuite, dans le composant de la barre d'outils lui-même, vous définirez un ViewContainerRef qui référencera l'emplacement dans votre modèle html où vous souhaitez que le composant dynamique s'affiche. Ensuite, la méthode updateToolbar() se chargera de créer votre composant, de le placer à l'emplacement spécifié et d'afficher le composant sur la barre d'outils.

@ViewChild('toolbarTarget', {read: ViewContainerRef}) toolbarTarget: ViewContainerRef;
...
updateToolbar(data: any) {
    this.clearComponent();
    this.component = data['toolbar'];
    if (this.component) {
      const childComponent = this.componentFactoryResolver.resolveComponentFactory(this.component);
      this.refInjector = ReflectiveInjector
          .resolveAndCreate([{provide: this.component, useValue: this.component}], this.injector);
      this.componentRef = this.toolbarTarget.createComponent(childComponent, 0, this.refInjector);
    }
  }

Votre composant créé dynamiquement sera alors placé là où vous mettez le #toolbarTarget correspondant

<div #toolbarTarget></div>

La méthode clearComponent() est appelée chaque fois que ngOnDestroy() est exécuté, ce qui se charge de masquer le composant lorsque vous quittez la page.

clearComponent() {
    this.toolbarTarget.clear();
    if (this.componentRef) { this.componentRef.destroy(); };
  }

  ngOnDestroy() {
    this.resolverSub.unsubscribe();
    this.clearComponent();
  }

J'ai également mis à jour mon message d'origine ci-dessus. J'ai reçu beaucoup de questions à ce sujet, donc j'ajouterai un exemple de travail en utilisant cette barre d'outils dynamique dès que possible et je vous mettrai à jour avec un lien.

@jmcclanahan que je reçois (je pense), mais je ne comprends toujours pas comment l'appel au service est effectué à partir du composant dynamique (la partie d'appel this.toolbarService.receiveContext ) puisque le ngOnInit n'est jamais appelé (ou je ne je ne vois pas comment faire). J'ai ajouté à entryComponents mais j'ai besoin de l'initialiser quelque peu pour que le onInit fonctionne.

@Dunos - Peut-être qu'un exemple sera le meilleur. Supposons que je navigue de la page d'index de mon application vers la page A. Vous aurez un itinéraire correspondant pour la page A, qui définira le module/composant de la page A. La page A aura également un composant distinct défini que je souhaite afficher dans la barre d'outils globale. Ainsi, dans le composant principal de la page A, vous allez injecter ComponentFactoryResolver et Injector , puis déclencher un événement receiveContext . Ce bout de code :

constructor(private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector) {}
...
ngOnInit() {
  this.toolbarService.receiveContext(this.componentFactoryResolver, this.injector);
}

Désolé, je pouvais voir comment cela pouvait prêter à confusion. Vous ne mettez pas ce code dans le composant que vous souhaitez afficher dans la barre d'outils, mais plutôt le composant associé à la page sur laquelle vous vous trouvez.

@jmcclanahan Ah parfait, merci beaucoup !!! Ça marche maintenant :)

@jmcclanahan Je viens de résoudre le même problème avant de trouver ce post. Une chose que j'ai remarquée est que si vous souhaitez enregistrer quelques caractères dans vos déclarations de fonction, vous pouvez obtenir une référence au ComponentFactoryResolver à l'aide de l'injecteur que vous passez. tu peux juste faire :
injector.get(ComponentFactoryResolver);

Et cela devrait renvoyer la référence dont vous avez besoin. Mais à la fin, cela donne le même résultat que de passer le ComponentFactoryResolver en tant que paramètre dans la fonction également.

Cela résout également mon problème.

En résumé, tout ce que vous devez savoir, c'est que lorsque vous souhaitez créer dynamiquement un composant déclaré dans un module paresseux, vous devez en fait utiliser ComponentFactoryResolver à partir d'un composant déclaré dans ce module paresseux. L'injecteur racine (qui contient le composant racine ComponentFactoryResolver) ne connaîtra aucun élément entryComponent déclaré dans un lazyModule chargé.

Cela semble un peu bizarre, mais c'est ainsi qu'Angular est conçu (ce qui est logique), mais cela crée en fait une complexité supplémentaire lorsque vous travaillez avec le framework, à mon humble avis.

Comment l'équipe Angular pourrait en tirer parti : chaque fois qu'une route est activée, l'injecteur racine doit ajouter automatiquement les composants d'entrée répertoriés sur ce lazyModule et chaque fois que la route est modifiée, les composants d'entrée précédemment ajoutés doivent être automatiquement détruits, cela garantirait moins de complexité et éviterait une inondation excessive. l'injecteur de racine avec des usines de composants supplémentaires.

Je vais soumettre une demande de fonctionnalité pour cela.

Merci!

Merci beaucoup @jmcclanahan

@jmcclanahan merci beaucoup !! @renatoaraujoc avez-vous soumis cette fonctionnalité à l'équipe Angular ?

@rraya J'ai soumis une demande de fonctionnalité concernant ce "problème" (ce n'est pas vraiment un problème si vous y réfléchissez), veuillez consulter # 17168.

Juste pour ajouter à cela. Mon cas d'utilisation est que j'ai créé un ModalManagerService dans mon AppModule qui a une méthode ouverte que toutes les autres parties de l'application appellent pour ouvrir un modal. Il reçoit un composant qui est ensuite ouvert en tant que modal. J'ai également une méthode closeAll qui est utilisée lorsque les modaux doivent être fermés par une action autre que l'utilisateur qui les ferme, par exemple un changement d'itinéraire en raison d'un délai d'expiration de la session.

@jmcclanahan mais je ne comprends toujours pas le vôtre, merci beaucoup s'il y a des suggestions. Il semble qu'une erreur se soit produite à 'this.componentFactoryResolver.resolveComponentFactory'

@Dunos pouvez-vous me donner un coup de main? J'ai souffert du même problème pendant des semaines, et c'est vraiment un mal de tête.

@werts ,

Tout ce que vous devez comprendre, c'est que chaque fois que vous naviguez vers une route chargée paresseusement, la route activée engendrera un injecteur enfant qui contient les composants d'entrée pour ce lazyModule. Si vous avez un modalService global défini dans l'AppModule (qui est l'injecteur racine de l'application), le componentFactoryResolver ne connaîtra aucun nouveau entryComponents d'aucun lazyModule. Ainsi, l'une des façons de résoudre ce problème est d'obtenir l'instance de complementFactoryResolver à partir d'un composant de lazyModule et de dire d'une manière ou d'une autre au modalService "hé, utilisez cet injecteur/componentFactoryResolver pour rendre le entryComponent souhaité".

Cela semble un peu difficile à comprendre, mais ce n'est pas le cas.

@renatoaraujoc merci pour votre explication, dans mon cas d'utilisation, je veux charger un composant à partir d'un lien, c'est-à-dire que je clique sur le menu dans la barre latérale gauche, et je vais récupérer le module à partir d'un module chargé paresseux, puis je vais créer un composant dynamique, parce que je veux créer une application de type onglet, donc, dans la bonne zone de contenu principale, il y a du contenu de composant, si je clique sur le déclencheur d'onglet, le contenu s'affichera. ou y a-t-il un onglet de création de solution- comme l'application via angular2/4 ?

de toute façon, le composant lazyload ne peut jamais être init, comment puis-je dire au service son componentFactoryResolver ?

pour mon application, il y a un AppModule, et il contient CoreModule, donc, mon ContentTabModule (utilisé pour charger l'onglet) appartient à CoreModule,, tous les composants de création gérés par ContentTabModule, il a un service, un composant, une directive, un tuyau, lorsque vous cliquez sur un menu lien, le ContentTabService créera un composant dynamique à partir du menu params.j'ai vu un tronc (j'utilise webpack) chargé sur le panneau réseau.et ensuite je veux créer un composant qui a démontré au module lazyload.

@renatoaraujoc comment puis-je obtenir l'instance de complementFactoryResolver à partir d'un composant d'un lazyModule? Je pense que c'est beaucoup plus difficile, car le composant du lazymodule ne sera jamais initialisé (ou je me trompe)

@werts vérifie la conversation que j'ai eue avec @jmcclanahan ci-dessus, nous avons parlé de quelque chose de similaire avec une barre d'outils.

@Dunos je ne comprends pas l'exemple de @jmcclanahan , je pense que je souffre de la même chose. Le composant lazyload ne s'initialise jamais.
this.toolbarService.receiveContext() appelé depuis le module lazyload ?

@werts vous devez charger le composant principal du module paresseux avec les routes, et ce composant est celui qui appelle le service aux avec son Injector et ComponentFactoryResolver.

@Dunos merci, pouvez-vous me donner un coup de main si cela vous convient, un autre cas d'utilisation, je veux charger paresseusement un module à partir d'un clic sur le lien, puis créer un composant dynamique. est-ce possible?

@werts ... oui, c'est possible. Regardez https://github.com/Toxicable/module-loading pour le point de départ.

ok, essayez-le maintenant, merci. @mlc-mlapis

@werts et al : Puis-je vous recommander de transférer ce fil d'assistance sur un autre canal ? StackOverflow ou Gitter pourraient être plus appropriés. Dans l'état actuel des choses, je suis abonné à ce numéro (comme je suis sûr qu'il y en a beaucoup d'autres) et il génère beaucoup de bruit en ce moment, ce qui devient un peu hors sujet.

Du côté positif : c'est formidable de voir une communauté aussi utile !

Je ne peux pas croire que ce problème soit également un autre obstacle auquel je dois faire face avec des modules chargés paresseux. J'ai désespérément cherché une solution pour garder les enfants de mon module paresseux anonymes de son parent (app.module) afin que je puisse facilement supprimer/exporter le module paresseux si nécessaire. Pour quelque chose d'aussi simple qu'une application large <router-outlet name="app-popup"> dans app.component.html (que j'ai abandonné dans l'espoir que ComponentFactory puisse remplacer son travail), le simple fait de charger des composants enfants paresseux a été très intimidant. Seulement pour rencontrer maintenant le problème des composants d'entrée avec les enfants d'entrée des modules paresseux. Il n'est tout simplement pas possible de garder mes modules isolés correctement si j'ai besoin du module racine pour enregistrer les composants enfants paresseux. J'espère juste que l'équipe Angular réalisera bientôt ce problème et le résoudra. J'attends toujours qu'ils répondent à mon problème avec une sortie de routeur à l'échelle de l'application pour les modules paresseux. https://github.com/angular/angular/issues/19520

Au début, il n'y avait rien,
'Créons une vue! ', déclare l'équipe jQuery,
'Faisons une liste ! ', déclare l'équipe d'Angular.

@jmcclanahan nous avons maintenant ngComponentOutlet, cela simplifie beaucoup les choses.

<ng-container  *ngComponentOutlet="yourComponentClass;injector: yourInjector;"></ng-container>

@crysislinux ngComponentOutlet est génial mais il ne nous permet pas encore de transmettre des données au composant, ce n'est donc pas une solution.

Des news sur cette fonctionnalité ? =/

Je viens de tomber sur ce problème. J'ai essayé quelques suggestions, sans succès. Je l'ai fait fonctionner, cependant. Il s'est en fait avéré qu'il manquait à mon module chargé paresseusement une importation d'un autre module dépendant - dans ce cas, le module modal NGX - mais l'erreur accusait mon composant de ne pas pouvoir être résolu.

Je suis curieux : ce problème est-il résolu par l'utilisation d'ui-router ? Ou peut-être même la capacité de multiples prises de routeur d'angular ? Font-ils cette logique dans les coulisses? C'est un cas très courant pour moi dans angular 1.x et j'essaie de le faire passer à angular 2+

Dans l'exemple de la barre d'outils, la vue ui (probablement une vue nommée, ou une sortie de routeur dans le cas d'un routeur angulaire), serait configurée dans un module de niveau supérieur. Si un état de module chargé paresseux remplace la barre d'outils ui-view par son propre contenu, il devrait simplement "fonctionner" ou ui-router ne fonctionnerait pas dans un environnement chargé paresseux où les vues de niveau supérieur en dehors du module actuel doivent être remplacées . Ce serait extrêmement limitant par rapport à angular 1.x.

L'exemple ci-dessus semble résoudre la même chose que ui-router/angular router sont conçus pour résoudre. Est-ce que je manque quelque chose?

... nous sommes probablement perdus quel est le problème du tout. Comme nous le savons et l'utilisons chaque jour, il n'y a aucun problème à utiliser des composants à partir de modules chargés paresseux ... même via un routeur ou par programmation par notre propre code.

Lorsque vous utilisez l'exemple @jmcclanahan ci-dessus, il semble bien fonctionner pour mon cas d'utilisation.

J'ai dû ajouter this.componentRef.changeDetectorRef.markForCheck(); après this.componentRef = this.toolbarTarget.createComponent(childComponent, 0, this.refInjector);.

Cela a résolu un problème que j'avais avec les propriétés HostBinding qui ne se déclenchaient pas.

J'ai pensé que je prendrais un swing à une solution potentielle qui a fonctionné pour moi. Ceci est un fork du plunkr original.
http://plnkr.co/edit/x3fdwqrFDQr2og0p6gwr?p=preview

Pour contourner ce problème, vous devez résoudre la fabrique de composants à partir d'un service (ResolverService) fourni dans le module qui a l'entryComponent (Page1Module). Ceci est ensuite transmis à un deuxième service (ChangerService) qui utilise la fabrique résolue et ajoute le composant à la vue sur le MainComponent.

Ce que je ne comprends vraiment pas, c'est ce putain de routeur. Vérifiez ce plunker (forké de l'exemple de @patkoeller )
http://plnkr.co/edit/keMuCdU9BwBDNcaMUAEU?p=preview @ src/page1.ts

Ce que je veux essentiellement faire est d'injecter un composant dans l'application de niveau supérieur et d'utiliser la directive [routerLink] avec des commandes relatives à son module chargé paresseux comme : [routerLink]="['./', 'sub', 'component'] pour générer /page-1/sub/component . Si je passe un injecteur à createComponent avec quelques ActivatedRoute et que cela fonctionne pour mon cas, cela bousille tous les autres routerLink, mais si je réaffecte router.routerState.snapshot au state de la résolution et passez l'injecteur avec cette instance de routeur, il semble qu'il fonctionne comme prévu... très déroutant !

Dois-je signaler un bogue ?

Je suis confronté au même problème avec les composants d'entrée pour mon service de singleton de dialogue. Les composants modaux liés aux fonctionnalités doivent rester dans leur module respectif sans avoir besoin de la solution de contournement de @jmcclanahan .

Un nouvel ETA à ce sujet ? Ou au moins une réponse de l'équipe Angular ? Ne pas avoir cette fonctionnalité est un vrai inconvénient car l'encapsulation du module vient d'être jetée par la fenêtre. Toutes les boîtes de dialogue pour @angular/material doivent être importées dans le module racine.

@ xban1x Je ne pense pas que nous ayons progressé à ce sujet. Il faut en faire le tour :(

@jmcclanahan est-il possible d'avoir une référence de composant injecté en utilisant votre approche ? J'ai injecté avec succès un composant, c'est un arbre btw, alors maintenant je dois m'abonner aux événements de clic d'arbre dans le composant, qui a injecté l'arbre. Existe-t-il un moyen d'accomplir cela?

Dans le numéro 23819, j'ai demandé à ce que les composants d'entrée se comportent de la même manière que les services avec providesIn: 'root' . Fermé ce problème car il semble que les deux problèmes visent à résoudre la même chose. J'espère que ce problème sera bientôt résolu car c'est une fonctionnalité importante :)

Mettre à jour

D'après ce que je vois dans le code non minifié, lorsque providesIn est défini dans le service, il enregistre l'usine afin qu'elle puisse déjà être utilisée (sans avoir besoin de l'importer dans un module), si j'ai compris correctement:

var Injectable = makeDecorator('Injectable', undefined, undefined, undefined, function (injectableType, options) {
    if (options && options.providedIn !== undefined &&
        injectableType.ngInjectableDef === undefined) {
        injectableType.ngInjectableDef = defineInjectable({
            providedIn: options.providedIn,
            factory: convertInjectableProviderToFactory(injectableType, options)
        });
    }
});

Je suppose qu'il existe un moyen d'appliquer une logique similaire dans un composant avec une option comme @Component({ ... entryComponent: true }) sorte que lorsque le morceau qui le contient est chargé, la fabrique est définie dans le résolveur (il devrait y avoir un module qui inclut le composant dans entryComponents avec les dépendances du composant, sinon cela provoquerait une erreur de compilation).

@lucasbasquerotto ... nous utilisons la logique suivante pour charger un module et utiliser ses types de composants (avec notre propre mappage entre les clés de chaîne <-> types de composants stockés directement dans ce module):

this._loader.load(url)
    .then((_ngModuleFactory: NgModuleFactory<any>) => {
        const _ngModuleRef: NgModuleRef<any> = _ngModuleFactory.create(this._injector);
        const _cmpType: Type<any> = _ngModuleRef.instance.cmps.get('dynamic');
        const _cmpFactory: ComponentFactory<any> = _ngModuleRef
            .componentFactoryResolver
            .resolveComponentFactory(_cmpType);
        this._cmpRef = this.vc.createComponent(_cmpFactory, 0, this._injector, []);
    });

Bonjour les gars, pour l'instant j'utilise également la solution de contournement consistant à avoir une classe singleton qui maintient une référence aux plusieurs résolveurs de modules chargés paresseux.
Existe-t-il un meilleur moyen de gérer cela avec la nouvelle version d'Angular ?
Merci

Salut tout le monde,

Juste face au même problème et j'utilise la solution de contournement comme amorce pour ma solution.

Une façon d'aller plus loin et de réduire le besoin d'avoir un service global, serait d'exposer la variable privée loadConfig de l'événement RouteConfigLoadEnd. Cette variable privée de type LoadedRouterConfig contient le module chargé, son injecteur et son componentFactoryResolver.

Dois-je ouvrir un nouveau sujet pour proposer cela en tant que demande de fonctionnalité ?
--------- Après modification

Peu importe ma question, j'ai trouvé cette demande de fonctionnalité https://github.com/angular/angular/issues/24069

Je pense que j'ai le même problème, mais mon module supplémentaire n'est pas lazy loaded . Je dois déplacer mon entryModules de SharedFeatureModule (qui n'est chargé que si nécessaire) à CoreModule .
Maintenant ça marche. Mais j'ai déplacé le composant lié aux fonctionnalités vers le noyau, ce qui n'est pas correct à mon avis.

Ce problème n'est toujours pas résolu ?

La solution de @jmcclanahan

{ path: '', component: yourComponent, data: { toolbar: yourToolbarComponentToInject } }

Ajoute une route vierge supplémentaire à la fin. Lorsque j'essaie de créer un fil d'Ariane dynamique, cela devient un problème.

Sans ajouter cette route vide par défaut au composant, je préfère avoir quelque chose de similaire à bootstrap dans les modules enfants paresseux.

Des mises à jour à ce sujet ?

Notre solution personnalisée implique l'injection par le constructeur des ComponentFactoryResolver et Injector dans chaque composant qui pourrait ouvrir notre barre latérale globale. Le composant appelant transmet ensuite ces références (les siennes) à un service avec le Component souhaité. Le service utilise ces instances pour créer le composant. Trop compliqué et trop verbeux.

Placez simplement vos composants d'entrée dans leur propre sous-module et importez-les dans l'application et nulle part ailleurs.

@broweratcognitecdotcom Je pense que le point de ce problème est pour entryComponents dans les modules chargés paresseux . L'inclusion de tous les sous-modules de chaque composant que vous souhaitez utiliser comme composant d'entrée dans AppModule forcera le chargement de tous ces composants avec impatience, ce qui pourrait entraîner des problèmes de performances au démarrage .

Cela ne veut pas dire que ces composants doivent être agnostiques vis-à-vis des composants qui les utilisent (les consommateurs), de sorte que leurs modules ne doivent pas savoir s'ils sont utilisés comme composants d'entrée ou composants normaux (déclarés dans le html), uniquement son consommateur devrait savoir (ainsi, les mettre dans AppModule rendrait difficile de savoir pourquoi ils sont là, vous auriez besoin de chercher dans d'autres endroits pour le savoir).

@lucasbasquerotto ... chargez ces modules paresseux par programme ... vous avez alors le contrôle total, et cela fonctionne.

Je suis tombé sur ce fil après avoir rencontré un problème étrange où je ne pouvais pas résoudre le composant à partir du résolveur racine en utilisant une carte des clés de chaîne et des composants correspondants. Je peux littéralement voir le composant dans la carte _factories, mais d'une manière ou d'une autre, il ne se compare pas correctement et renvoie undefined.

@jmcclanahan @Dunos Ne pensez-vous pas qu'au lieu que le module de fonctionnalités remette sa référence d'injecteur (enregistrant son contexte avec le module d'application principal), il serait préférable qu'il expose un service, par exemple FactoryResolver, qui renverra en fait l'usine correctement résolue pour son composants car il aura une référence correcte à l'injecteur de son propre module. De cette façon, ce sera comme exposer une API publique pour vos composants.

J'essaie de faire quelque chose de similaire, mais mon module n'est pas chargé paresseux et je me heurte au problème que j'ai décrit ci-dessus. En théorie, cela devrait fonctionner, à moins qu'il ne me manque quelque chose d'évident ici?

@h-arora J'ai remarqué le même problème en voyant la maquette. dans _factories, mais le problème était que le module avait une erreur de construction en fait, qui n'apparaissait pas

Coping over from #17168 (fermé en double de celui-ci)

Comportement actuel
Si vous avez un service singleton qui utilise un ComponentFactoryResolver fourni par le RootInjector de l'application pour créer dynamiquement des composants et que vous souhaitez créer un entryComponent déclaré dans un module paresseux, cela échouera car l'injecteur racine ne le sait pas.

Comportement prévisible
C'est en fait le comportement attendu étant donné que l'injecteur racine ne connaîtra pas les composants d'entrée connus par l'injecteur enfant.

Quelle est la motivation / le cas d'utilisation pour changer le comportement ?
La motivation est de tirer parti de la complexité pour créer des composants dynamiques bien que la conception modulaire d'Angular soit correcte.

Version angulaire : 2.4.9

Solution proposée : même si cela va à l'encontre de la conception modulaire adoptée par Angular lui-même, je pense que la complexité fournie pour créer des composants dynamiques pourrait être adoucie en ajoutant simplement les entryComponents du LazyModule aux entryComponents du RootInjector et pour éviter d'inonder le RootInjector, chaque fois que vous naviguez (qui détruit le LazyModule), les entryComponents précédemment injectés seraient effacés du tableau.

En parlant simplement :

La page est chargée -> RootInjector est créé.
Émet une navigation qui active un LazyModule -> ChildInjector est créé.
RootInjector.entryComponents.append(childInjector.entryComponents)
Émet une navigation vers un autre endroit -> LazyModule n'est plus nécessaire, donc détruit.
RootInjector.entryComponents.destroyLastAppendedEntryComponents()
J'espère pouvoir suggérer quelque chose de bien.

J'ai passé quelques jours à décomposer le projet de mon entreprise et à mettre en œuvre le chargement paresseux. Ce n'est qu'à la fin que j'ai réalisé que nos modaux ne fonctionnaient pas en raison d'entrées dans leurs modules respectifs. Nous sommes revenus en arrière et attendons un correctif...

Est-ce quelque chose qui est prévu ou devrions-nous explorer une solution de contournement. Comme les gens le font ci-dessus.

@ravtakhar , vous voudrez probablement examiner la solution de contournement car elle se trouve dans le backlog et n'a pas de priorité attribuée pour le moment.

J'ai passé quelques jours à décomposer le projet de mon entreprise et à mettre en œuvre le chargement paresseux. Ce n'est qu'à la fin que j'ai réalisé que nos modaux ne fonctionnaient pas en raison d'entrées dans leurs modules respectifs. Nous sommes revenus en arrière et attendons un correctif...

Est-ce quelque chose qui est prévu ou devrions-nous explorer une solution de contournement. Comme les gens le font ci-dessus.

Même problème ici

@tmirun ... si vous chargez de tels modules paresseux par programme ... vous avez alors le contrôle total, ... cela signifie que vous pouvez enfin obtenir une référence de module et appeler moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponentType)

Avait le même problème que beaucoup ci-dessus, et résolu en créant un module autonome déclarant et exportant tous les entryComponents, y compris celui de l'appModule et en l'exportant également, de sorte que chaque module lazyLoaded charge ces composants.

La seule autre solution réalisable que j'ai trouvée consistait également à générer des boîtes de dialogue en fournissant un ComponentFactoryResolver personnalisé selon le module hébergeant le entryComponent .

ATTENTION : rappelez-vous que les modules singleton fournis à la racine ne verront que les principaux composants d'entrée du module , c'est pourquoi j'ai décidé de proposer la première solution, sinon vous pourriez penser à créer un générateur de dialogue pour chaque sous-module de votre application et, dans ce sous-module, éventuellement inclure des modules partagés qui ont d'autres entryComponents , en fournissant au service engendrant les modaux les ComponentFactoryResolver desdits modules.

... la solution générale prise en charge devrait être disponible (quelques nouvelles API + prise en charge dans Ivy) ... en raison de la présentation de Jason http://www.youtube.com/watch?v=2wMQTxtpvoY&t=131m24s ... dans Angular 7.2 . .. mais je ne sais pas si la version 7.2 a été mentionnée correctement.

J'ai le même problème avec CDK Overlay , j'ai un plugin de galerie qui est construit au-dessus de la superposition CDK, mais à cause de ce problème, je dois l'importer dans le module racine.

BTW, existe-t-il une solution de contournement pour mon cas?

@MurhafSousli ... lol, je dois dire encore une fois que c'est voulu. La chose a été expliquée cent fois ... il y a un injecteur racine et tout module chargé paresseux est d'un niveau de profondeur, vous ne pouvez donc pas supposer que vous pouvez toucher ses composants à partir du niveau racine. Mais comme cela a été dit à plusieurs reprises, vous pouvez charger ce module par programme ... vous obtiendrez donc cette référence de module ... et vous pourrez alors accéder à ses composants.

Ajoutez moi aussi à la liste 😄. J'ai un composant modal dans lequel je charge mes composants de manière dynamique et je ne veux pas tous les charger dans l'application, ce qui serait une mauvaise conception car ils ne font pas partie du composant de l'application. Mais c'est ce que je dois faire je suppose après avoir lu tout cela.

@msarica m'a fait remarquer aujourd'hui que si vous créez un service chargé paresseusement et que vous le faites étendre votre service global qui ouvre des fenêtres contextuelles, puis utilisez le service chargé paresseusement pour ouvrir la fenêtre contextuelle, cela fonctionnera comme prévu :

Mondial PopupService

@Injectable({ providedIn: 'root' })
export class PopupService {
  constructor(
    private injector: Injector,
    private overlay: Overlay,
  ) {}

  open<T = any>(component: ComponentType<T>) { ... }
}

Service spécifique uniquement chargé paresseusement dans MyFeatureModule

@Injectable()
export class MyFeaturePopupService extends PopupService {}

Utilisation dans un composant qui fait partie du MyFeatureModule chargé paresseusement

@Component({ ... })
export class MyFeatureComponent {
  constructor(
    private myFeaturePopupService: MyFeaturePopupService,
  ) {}

  openPopup() {
    this.myFeaturePopupService.open(MyFeaturePopupComponent);
  }
}

Exemple de définition de module chargé paresseusement

@NgModule({
  imports: [MyFeatureRoutingModule],
  declarations: [
    MyFeatureComponent,
    MyFeaturePopupComponent,
  ],
  entryComponents: [MyFeaturePopupComponent],
  providers: [MyFeaturePopupService],
})
export class MyFeatureModule {}

Testé en angulaire 7.

Remarque : Notre code exploite le Overlay du CDK angulaire, mais le concept est le même (selon d'autres parties de ce fil, c'est le Injector qui est préoccupant).

... la solution généralement prise en charge devrait être disponible (quelques nouvelles API + prise en charge dans Ivy) ... dans Angular 7.2

... @mlc-mlapis pourriez-vous développer la solution générale prise en charge ?

@cedvdb ... Je viens de mentionner le moment important de cette présentation qui ressemble en fait à une solution ... cela signifie la capacité prise en charge de charger par programmation un module chargé paresseux, puis d'en utiliser n'importe quelle partie, comme un composant, une directive, ...

Cette méthode est disponible encore aujourd'hui ... mais avec Angular CLI, vous devez toujours définir les routes nécessaires ... même si vous ne les utilisez jamais via le routeur.

Wow. C'était très inattendu.

J'ai implémenté mon propre système de dialogue basé sur des portails et des superpositions @angular/cdk : https://stackblitz.com/edit/cdk-dialog-example-p1. J'ai aussi ce problème.

La solution de contournement mentionnée par @CWSpear fonctionne : j'ai dû étendre le service dans mon module de fonctionnalités et cela a fonctionné pour moi. Comme c'est bizarre !

Existe-t-il une solution plus élégante pour cela?

Existe-t-il une solution plus élégante pour cela?

Quelque chose que j'ai expérimenté consiste à fournir à nouveau un wrapper de lanceur au niveau du module paresseux. Demandez à ce wrapper de prendre un injecteur dans le constructeur et de le transmettre au véritable lanceur (qui est injecté à partir de la racine). C'est un correctif d'une ligne. Je ne suis pas sûr de sa stabilité, cependant,

@NgModule({
  declarations: [LazyRootComponent, LazyEntryComponent],
  entryComponents:[LazyEntryComponent],
  providers:[LauncherWrapper],  //<---- reprovision here 
  imports: [
    CommonModule,
    LazyModRoutingModule
  ]
})
export class LazyModModule { }
@Injectable()
export class LauncherWrapper {
    constructor(private injector: Injector,  private launcher:ActualLaucherClass) {
    }

Mon problème a été causé parce que j'ai utilisé @Injectable({providedIn: 'root'}) . Le changer en @Injectable({providedIn: MyModule}) l'a résolu. 😄

Cela a du sens, car le service a besoin d'accéder aux composants d'entrée des modules chargés paresseux. Lorsqu'il est fourni à la racine, il ne peut pas y accéder car le module est temporairement déconnecté de l'injecteur racine.

Un grand merci à @jmcclanahan et @dirkluijk , je me suis gratté la tête pendant des heures et votre description m'a aidé à trouver une solution à mon cas.

Je me demande juste quelle est la bonne façon de le faire.
Il faut @angular/cdk ? Pourquoi ?

Je ne pense pas qu'une solution de contournement soit viable si elle nécessite que le code que vous intégrez le sache, car il existe de nombreux codes tiers qui utilisent simplement ComponentFactoryResolver sans connaître votre registre alternatif spécial.

Dans cet esprit, voici ma "solution": CoalescingComponentFactoryResolver . Il s'agit d'un service qui doit être fourni par le module app et initialisé dans son constructeur, comme ceci :

@NgModule({
 providers: [CoalescingComponentFactoryResolver]
})
class AppModule {
  constructor(coalescingResolver: CoalescingComponentFactoryResolver) {
    coalescingResolver.init();
  }
}

Ensuite, les modules chargés paresseux devraient l'injecter et enregistrer leurs propres instances ComponentFactoryResolver avec. Ainsi:

@NgModule({})
export class LazyModule {
  constructor(
    coalescingResolver: CoalescingComponentFactoryResolver,
    localResolver: ComponentFactoryResolver
  ) {
    coalescingResolver.registerResolver(localResolver);
  }
}

Lorsque cela est fait, les composants d'entrée dans le module à chargement différé doivent être disponibles à partir de services à chargement non différé.

Comment ça marche : une fois initialisé, il injecte le ComponentFactoryResolver de l'application racine et le singe corrige la méthode resolveComponentFactory pour appeler sa propre implémentation. Cette implémentation essaie d'abord de résoudre la fabrique de composants sur tous les résolveurs de modules paresseux enregistrés, puis revient au résolveur d'application racine (il y a un peu de logique supplémentaire pour éviter les appels cycliques, mais c'est l'essentiel).

Donc, oui, un hack assez grossier. Mais cela fonctionne, en ce moment, dans Angular 7. Peut-être que cela servira à quelqu'un.

En m'appuyant sur la solution de @CWSpear , j'ai réussi à le faire fonctionner en ajoutant simplement mon PopupService existant au tableau de fournisseurs du composant. Je n'ai donc pas eu besoin de créer un PopupService "spécifique à une fonctionnalité" séparé.

Alors comme ça :

@Component({
  ...
  providers: [PopupService]  //<---- generic PopupService
})
export class MedicationPortletComponent implements OnInit, OnDestroy {
...

Alternativement, vous pouvez également ajouter le PopupService au tableau de fournisseurs du NgModule du module chargé paresseux. Par exemple:

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
  ],
  providers: [PopupService] //<--- here's the PopupService!
})
export class SharedFeaturePatientModule {}

@jonrimmer meilleure solution

@jonrimmer Ceci est peu intrusif et fonctionne parfaitement pour notre cas d'utilisation. Un grand merci de ma part et de mon équipe :)

Je suis juste curieux de savoir comment cela fonctionne correctement en cas de routeur paresseux, mais cela génère une erreur lorsque j'ai supprimé la route paresseuse et chargé les modules manuellement à l'aide de ngModuleFactoryLoader.

//Ignore the syntax
CompA {
  openDialog() {//invoked on button click of this comp
    matDialog.open(FilterComponent);//works fine in case of lazy route but throws error when manually loaded
  }
}

ModuleA {//lazy module
  imports: [MatDialog, FilterModule],
  declaration: [CompA]
}

FilterModule {
  declaration: [FilterComponent],
  entryComponent: [FilterComponent]
}

FilterComponent { ...
}

@jmcclanahan @MartinJHItInstituttet @splincode

async showBar() {
    const module = await import("../bar/bar.module");
    const compiled = this._compiler.compileModuleAndAllComponentsSync(
      module.BarModule
    );
    compiled.ngModuleFactory.create(this._injector);

    let factory: any = compiled.componentFactories.find(
      componentFactory => componentFactory.selector === "app-dialog"
    );
    this.modal.show(factory.componentType);
  }

@Jimmysh Ceci est pour le mode JIT. Vous devez charger import("../bar/bar.module.ngfactory") pour le mode AOT (où compileModuleAndAllComponentsSync n'est pas disponible).

@mlc-mlapis
fortement inspiré par https://github.com/angular/angular/blob/master/aio/src/app/custom-elements/elements-loader.ts

https://github.com/Jimmysh/ng-lazy-component-load

Il peut utiliser AOT JIT. succès pour moi.

import { InjectionToken, Type } from '@angular/core';
import { LoadChildrenCallback } from '@angular/router';

export const ELEMENT_MODULE_LOAD_CALLBACKS_AS_ROUTES = [
  {
    path: 'aio-foo',
    loadChildren: () => import('../foo/foo.module').then(mod => mod.FooModule)
  }
];

export interface WithCustomElementComponent {
  customElementComponent: Type<any>;
}

export const ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN = new InjectionToken<Map<string, LoadChildrenCallback>>(
  'aio/elements-map'
);

export const ELEMENT_MODULE_LOAD_CALLBACKS = new Map<string, LoadChildrenCallback>();
ELEMENT_MODULE_LOAD_CALLBACKS_AS_ROUTES.forEach(route => {
  ELEMENT_MODULE_LOAD_CALLBACKS.set(route.path, route.loadChildren);
});

```dactylographié
import { Compiler, Inject, Injectable, NgModuleFactory, NgModuleRef, Type, ComponentFactory } from '@angular/core' ;
import { LoadChildrenCallback } de '@angular/router' ;

importer { ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN } à partir de './lazy-component-registry' ;

@Injectable({
fourni dans : 'racine'
})
classe d'exportation LazyComponentLoader {
modules privés à charger : carte;
modules privésChargement = nouvelle carte>();

constructeur(
moduleRef privé : NgModuleRef,
@Inject(ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN) elementModulePaths : mapper,
compilateur privé : compilateur
) {
this.modulesToLoad = new Map(elementModulePaths);
}

load(chemin : chaîne, sélecteur ? : chaîne): promesse{
si (this.modulesLoading.has(chemin)) {
return this.modulesLoading.get(path);
}

if (this.modulesToLoad.has(path)) {
  const modulePathLoader = this.modulesToLoad.get(path);
  const loadedAndRegistered = (modulePathLoader() as Promise<NgModuleFactory<any> | Type<any>>)
    .then(elementModuleOrFactory => {
      if (elementModuleOrFactory instanceof NgModuleFactory) {
        return elementModuleOrFactory;
      } else {
        return this.compiler.compileModuleAsync(elementModuleOrFactory);
      }
    })
    .then(elementModuleFactory => {
      const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector);
      const factories: Map<any, ComponentFactory<any>> = (elementModuleRef.componentFactoryResolver as any)
        ._factories;
      if (selector) {
        const find = Array.from(factories.keys()).find(type => {
          const factory = factories.get(type);
          return factory.selector === selector;
        });
        if (find) {
          return find;
        } else {
          return Promise.reject(new Error(`not found selector:${selector}`));
        }
      }
      this.modulesToLoad.delete(path);
      return;
    })
    .catch(err => {
      this.modulesLoading.delete(path);
      return Promise.reject(err);
    });
  this.modulesLoading.set(path, loadedAndRegistered);
  return loadedAndRegistered;
}
return Promise.resolve();

}
}

``` typescript
  async showFoo() {
    await this.lazyComponentLoader.load('aio-foo');
    this.modal.show(FooDialogComponent);
  }
  async showFoo2() {
      const aaa = await this.lazyComponentLoader.load('aio-foo', 'app-dialog');
    this.modal.show(aaa);
  }

@Jimmysh ... alors correct. Il utilise le compilateur uniquement en mode JIT.

constructor(private compiler: Compiler) {}
...
if (elementModuleOrFactory instanceof NgModuleFactory) {
   return elementModuleOrFactory;
} else {
   return this.compiler.compileModuleAsync(elementModuleOrFactory);
}

@mlc-mlapis AOT sera NgModuleFactory. Vous pouvez tester https://github.com/Jimmysh/ng-lazy-component-load

Hé les gars, avec l'aide des commentaires de ce fil, j'ai créé un package qui est censé être une solution de contournement pour essayer de charger des composants d'entrée en dehors du module via le routage. Veuillez y jeter un œil et espérons que cela vous aidera.

Lien référentiel : https://github.com/Jonathan002/route-master-example
URL de démonstration : https://jonathan002.github.io/route-master-example/

FWIW, dans la même veine que @CWSpear et @dirkluijk , j'ai supprimé le providedIn: 'root' de mon service (défini dans un module chargé paresseux et responsable de l'ouverture d'un entryComponent ) et à la place spécifié le service dans le providers de ma définition de module paresseux. Je crois que le plus gros/seul inconvénient de ceci est que le service n'est plus ébranlable.

L'utilisation providedIn: MyLazyModule a conduit à des avertissements de dépendances circulaires.

@jonrimmer Je ne pouvais pas quitter le fil sans vous remercier !! tu as sauvé ma journée.
Pour tous ceux qui lisent mon commentaire, cela peut vous aider à ne pas perdre votre temps si vous regardez cette merveilleuse réponse .

Confirmé que cela n'est pas encore résolu dans Angular 8.2.0. Depuis la création de l'OP il y a 2,5 ans, le paysage d'Angular avait beaucoup changé, et Angular Material 2 était devenu Angular Components.

Comme indiqué par les autres commentateurs, Angular Material utilise DOM afin que les développeurs d'applications n'aient pas à utiliser DOM lors du chargement d'un composant d'application dans MatDialog, même si le composant d'application est déclaré dans entryComponents du sous-module chargé paresseusement.

Cependant, faire les mêmes choses DOM dans une application métier semble étrange, contrairement aux directives de programmation d'application d'Angular.

Ce serait bien que la demande de fonctionnalité de l'OP puisse être implémentée afin que ComponentFactoryResolver puisse "voir" un composant d'entrée déclaré dans un sous-module chargé paresseusement, d'une manière ou d'une autre. Déclarer un composant d'entrée d'un module paresseux dans app.module fonctionne, mais moche.

En plus des solutions de travail sales mentionnées ci-dessus : 1. DOM, 2. Déclarer le composant dans app.module, je proposerais une autre solution, moins sale, ci-dessous.

la 3ème solution, moins sale.

J'ai un service partagé utilisé par plusieurs modules chargés paresseusement.

export interface DataComponent {
    data: any;
}

/**
 * Display an NG component in a dialog, and this dialog has not need to answer but close.
 */
@Injectable()
export class DataComponentDialogService {
    modalRef: MatDialogRef<DataComponentDialog>;

    constructor(private dialog: MatDialog) { }

    open(title: string, externalComponentType: Type<DataComponent>, componentData: any, autofocus = true): Observable<any> {
        const isSmallScreen = window.innerWidth < 640 || window.innerHeight < 640;
        this.modalRef = this.dialog.open(DataComponentDialog,
            {
                disableClose: true,
                minWidth: isSmallScreen ? '98vw' : undefined,
                autoFocus: autofocus,
                data: {
                    title: title,
                    externalComponentType: externalComponentType,
                    componentData: componentData,
                    isSmallScreen: isSmallScreen
                }
            });

        return this.modalRef.afterClosed();

    }

}

Ce service peut voir les composants d'entrée des modules non chargés paresseusement. J'ai des composants d'entrée implémentés dans certains modules chargés paresseusement. J'ai donc dérivé la classe de ce service à fournir dans chaque module paresseux :

import { DataComponentDialogService } from '../_ui_services/dataComponentDialog.component';

@Injectable()
export class LazyComponentDialogService extends DataComponentDialogService {
    constructor(dialog: MatDialog) {
        super(dialog);
    }
}

Étant donné que LazyComponentDialogService est fourni dans le même module différé avec le composant d'entrée différée, il peut donc voir le composant.

Selon l'architecture de votre application, vous n'aurez peut-être pas besoin de la classe dérivée, mais fournissez simplement DataComponentDialogService ou similaire dans chaque module.

Il semble que le problème n'existe plus dans Angular 9 en raison de l'implémentation de lierre (testé avec 9.0.0-next.7). Vous pouvez créer dynamiquement un composant d'un module chargé paresseux depuis l'extérieur du module paresseux. Il semble que la déclaration de composant d'entrée ne soit plus nécessaire pour les composants dynamiques dans Ivy. Ivy n'a pas besoin d'une fabrique de composants séparée qui était auparavant générée par le compilateur. Les informations sur la fabrique de composants sont intégrées dans la définition du composant avec Ivy(?). Une fois que vous avez un type de composant, vous devriez pouvoir obtenir sa fabrique de composants, puis en créer une instance. C'est juste mon impression. Prends ma parole avec un grain de sel

Tout ce que vous avez à faire est de créer un module de widgets séparé qui déclare tous vos composants dynamiques et les inclut également en tant que entryComponents. Importez ensuite ce module depuis AppModule. Cela garantira que vos composants d'entrée sont enregistrés auprès de l'injecteur racine.

Cette solution est propre et ne casse pas l'encapsulation paresseuse.

@NgModule({
  declarations: [
    ModalComponent,
    ... more dynamic components ...
  ],
  entryComponents: [
     ModalComponent,
    ... more dynamic components ...
  ]
})
export class DynamicModule {

}

Module d'application :

@NgModule({
   declarations: [
      AppComponent
   ],
   imports: [
      BrowserModule,
      AppRoutingModule,
      BrowserAnimationsModule,
      DynamicModule
   ],
   providers: [],
   bootstrap: [
      AppComponent
   ]
})
export class AppModule { }

@pixelbits-mk Cela rendrait tous les composants dynamiques (comme modal et similaires) chargés avec impatience , mais le but ici est de leur permettre d'être chargés paresseux pour ne pas ralentir le chargement initial de l'application (surtout à mesure que le projet grandit ), tout comme les composants qui les appellent.

Disons qu'un ComponentA chargé paresseux appelle dynamiquement un ComponentB chargé paresseux. Si ComponentA sait qu'il appellera ComponentB , vous pouvez simplement importer ComponentBModule dans ComponentAModule . Ce n'est pas le cas idéal, mais je peux vivre avec.

Le principal problème survient (à mon avis) lorsque ComponentA ne sait pas que ComponentB sera chargé . Par exemple, si ComponentB est un modal et que ComponentA appelle une méthode showModal() dans un service, et que le service charge ComponentB dynamiquement en arrière-plan, ComponentA ne le sauraient pas.

Ce que je fais maintenant, c'est inclure la référence ComponentB en tant que paramètre dans la méthode, comme showModal(ComponentB) , de telle manière que ComponentA sait maintenant que le ComponentB sera chargé, mais c'est mauvais à mon avis (chaque appel à la méthode doit passer la référence du composant).

Si les composants d'entrée étaient disponibles sans avoir à importer explicitement leurs modules (ce que je pense que ce problème concerne), cela le résoudrait (peut-être qu'Angular pourrait avoir un moyen de détecter que ComponentA appelle un service qui importe ComponentB et make ComponentAModule import implicitement ComponentBModule , et bien que cela puisse avoir l'effet indésirable d'importer également des modules de composants dont les références sont importées mais la raison de l'importation n'est pas de les créer dynamiquement , je verrais cela plus comme un cas marginal et je serais d'accord).

Je n'ai pas (encore) testé la suggestion @jonrimmer , mais cela semble prometteur.

Ce serait génial si ComponentA chargeait automatiquement le module paresseux avec ComponentB uniquement si ComponentA était affiché :
ModuleA a un composant ComponentA1 , ComponentA2 .
Référence ComponentA1 en html ComponentB (dans le module ModuleB ), mais pas ComponentA2 .
ModuleB serait automatiquement lazy load uniquement si ComponentA1 est affiché, mais pas si ComponentA2 est affiché.

Cela conviendrait parfaitement au scénario des tableaux de bord sur la page d'accueil. Dahsboards provient de nombreux modules différents, mais si le tableau de bord n'est pas inclus (disons via les paramètres utilisateur), il n'est pas nécessaire de télécharger ce module.

@jonrimmer Excellente idée d'envelopper la fonction resolveComponentFactory du principal ComponentFactoryResolver et de rechercher les modules de chargement paresseux enregistrés avant.

Il fonctionne avec des composants d'entrée qui injectent des services fournis en racine. Mais je ne peux pas injecter un service qui n'est fourni que dans le module de chargement différé. Dans ce cas, j'ai eu une StaticInjectorError. Pouvez-vous s'il vous plaît vérifier?

Je voulais confirmer que le lierre semble s'occuper de ce problème. Je viens de tomber sur ce problème aujourd'hui lorsque j'ai réécrit mes modules à charger paresseux qui contenaient des composants d'entrée et des services partagés pour les instancier uniquement pour trouver une nouvelle erreur indiquant que l'usine de composants était introuvable. En fait, j'ai été choqué de lire que c'était un problème en premier lieu.

Quoi qu'il en soit, je viens d'activer le lierre dans mon projet angulaire 8.2 existant et tout semble fonctionner comme prévu.

Ma solution pour utiliser le Overlay dans les modules chargés paresseux :
Vous devez passer le résolveur d'usine au constructeur de ComponentPortal comme ceci

@Injectable()
export class OverlayService {

  constructor(
    private overlay: Overlay,
    private componentFactoryResolver: ComponentFactoryResolver
  ) {
  }

  open<T>(component: ComponentType<T>) {
    const overlayRef = this.overlay.create();

    const filePreviewPortal = new ComponentPortal(component, null, null, this.componentFactoryResolver);

    overlayRef.attach(filePreviewPortal);
  }
}

mais vous devez également fournir OverlayService dans le module chargé paresseux. Il est nécessaire que OverlayService injecte ComponentFactoryResolver du module dans lequel OverlayService est fourni

@NgModule({
  declarations: [
     SomeComponent,
  ],
  entryComponents: [
    SomeComponent,  // <-------- will be opened via this.overlayService.open(SomeComponent)
  ],
  providers: [
    OverlayService, // <--------
  ]
})
export class LazyLoadedModule {}

@mixrich Cette approche fonctionnera [dans mon application, je fais comme ça et ça marche bien jusqu'à ce que je doive fermer toutes les boîtes de dialogue modales sur un événement comme la déconnexion] mais gardez à l'esprit que cela instanciera le OverlayService à chaque fois quand un autre module importe ce module, c'est-à-dire que l'application n'aura pas une seule instance comme les services l'étaient auparavant.

Comme il n'y aura pas de signal de service de superposition, il ne sera PAS facile de savoir combien de boîtes de dialogue modales sont ouvertes.

Bien sûr, ce sont les exigences de l'application, mais soyez prudent avec cette approche.

@mixrich Cette approche fonctionnera [dans mon application, je fais comme ça et ça marche bien jusqu'à ce que je doive fermer toutes les boîtes de dialogue modales sur un événement comme la déconnexion] mais gardez à l'esprit que cela instanciera le OverlayService à chaque fois quand un autre module importe ce module, c'est-à-dire que l'application n'aura pas une seule instance comme les services l'étaient auparavant.

Comme il n'y aura pas de signal de service de superposition, il ne sera PAS facile de savoir combien de boîtes de dialogue modales sont ouvertes.

Bien sûr, ce sont les exigences de l'application, mais soyez prudent avec cette approche.

Par exemple, dans mon cas, j'ai ModalDialogModule avec OverlayService fourni et lorsque j'appelle OverlayService.open(SomeComponent) , il crée également pour moi le modèle de fenêtre modale, insère SomeComponent à l'intérieur et renvoie une structure de données avec des observables utiles (pour les événements de clôture et de réussite), une instance de composant et une méthode manuelle close .
Ainsi, lorsque j'ai besoin d'utiliser des modaux dans mon LazyModule, il me suffit d'importer le ModalDialogModule pour obtenir la possibilité d'utiliser OverlayService . J'ai trouvé cette approche pratique, car vous savez toujours que pour utiliser modal, vous devez importer le ModalDialogModule , comme vous savez toujours que pour utiliser des formulaires réactifs, vous devez importer ReactiveFormModule

Je l'ai fait fonctionner dans Angular 8.3.6. J'ai un module de bibliothèque avec des composants d'entrée (dialogues de tapis) qui ne se chargeront pas si je les ajoute aux composants d'entrée des modules de bibliothèque. Il indique que la fabrique de composants pour MyCustomDialog est introuvable dans le journal de la console lorsque j'ai essayé de l'ouvrir.

La solution consiste à créer une méthode statique dans la classe NgModule du module de bibliothèque qui renvoie un tableau de tous les composants d'entrée du module. Appelez ensuite cette méthode à partir des modules d'application NgModule.

@NgModule({
    declarations: [
        MyCustomDialogComponent
    ],
    imports: [
        CommonModule
    ],
    exports: [
        MyCustomDialogComponent
    ]
    // no need to add the dialog component to entryComponents here.
})
export class MyCustomModule {
    static getEntryComponents() {
        const entryComponents = [];
        entryComponents.push(MyCustomDialogComponent);
        return entryComponents;
    }
}

import {MyCustomModule} from 'my-custom-library'; // import library from node_modules

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        AppRoutingModule,
        BrowserAnimationsModule,
        MyCustomModule
    ],
    providers: [],
    bootstrap: [AppComponent],
    entryComponents: [].concat(MyCustomModule.getEntryComponents())
})
export class AppModule {
}

Je sais que cela ne résout toujours pas les composants d'entrée de la bibliothèque qui ne fonctionnent pas, mais au moins avec la méthode statique, le module d'application n'a pas à connaître les composants d'entrée de la bibliothèque personnalisée. Il appelle simplement cette méthode et obtient tout ce qui est répertorié dans la bibliothèque. Il n'y a aucune responsabilité sur l'application pour savoir quels composants de la bibliothèque sont des composants d'entrée.

@ asmith2306 pourquoi n'utilisez-vous pas l'opérateur de propagation ? ça a l'air moche

@ asmith2306 pourquoi n'utilisez-vous pas l'opérateur de propagation ? ça a l'air moche

Parce que ça ne marche pas avec. Il y a un bug à ce sujet quelque part.

on dirait que ça ne marche pas avec le mode --prod

Même si cela a fonctionné, cela ne résout pas le VRAI problème. le problème est que si j'ajoute un composant aux entryComponents d'un NgModule et que ce module est importé dans un module chargé paresseusement dans mon application, alors les entryComponents doivent être automatiquement enregistrés avec les composants d'entrée des applications. Aucun autre travail ne devrait être nécessaire. Je dirais en outre que tout composant dans entryComponents de n'importe quel NgModule devrait automatiquement se retrouver dans entryComponents du module d'application. Ce n'est actuellement pas le cas. Si c'était le cas, nous ne serions pas ici.

@broweratcognitecdotcom Qu'en est-il des conflits et des priorités des différents niveaux d'injection ?

La commande @ mlc-mlapis devrait être selon le principe du premier arrivé, premier servi à mon avis.

@mlc-mlapis Voulez-vous dire si différents composants portent le même nom ? Première correspondance ou erreur s'il y a plusieurs correspondances. Je pourrais vivre avec une exigence selon laquelle les noms des composants d'entrée doivent être uniques.

@broweratcognitecdotcom Salut, oui. Parce que toutes les variantes doivent être couvertes si nous parlons de principes généraux.

@ mlc-mlapis Je pense que la limitation angulaire que beaucoup aimeraient surmonter est que les composants d'entrée ne peuvent être instanciés et placés dans le dom que par l'application. L'application n'a pas besoin de connaître une boîte de dialogue utilisée par un module que j'importe dans un module chargé paresseux. Le concept actuel des composants d'entrée rompt le modèle modulaire d'angular.

AFAIK avec Ivy entryLes composants ne seront plus nécessaires, n'est-ce pas ? Étant donné que les composants auront une localité, leur importation dynamique fonctionnera toujours?

@Airblader Vous avez raison, nous le savons. Ce n'était qu'un petit rappel de l'histoire. 😄

Pour ceux qui sont encore aux prises avec ces problèmes, voici la solution qui fonctionne réellement : https://github.com/angular/angular/issues/14324#issuecomment -481898762

Je publie un package pour ces problèmes. une copie de code de jonrimmer.
Il semble que le problème n'existe pas dans Angular 9. Si vous avez besoin de résoudre ce problème maintenant. vous pouvez utiliser @aiao/lazy-component
exemple de code ici
https://github.com/aiao-io/aiao/tree/master/integration/lazy-component

Je crois que cela est corrigé dans Angular v9 exécutant ivy. Si quelqu'un rencontre encore des problèmes similaires, veuillez ouvrir un nouveau problème avec un scénario de reproduction minimal. Merci !

Ce problème a été automatiquement verrouillé en raison de son inactivité.
Veuillez signaler un nouveau problème si vous rencontrez un problème similaire ou connexe.

En savoir plus sur notre politique de verrouillage automatique des conversations .

_Cette action a été effectuée automatiquement par un bot._

Cette page vous a été utile?
0 / 5 - 0 notes