Angular: Los componentes de entrada de un NgModule Lazy Loaded no están disponibles fuera del módulo

Creado en 6 feb. 2017  ·  122Comentarios  ·  Fuente: angular/angular

Estoy enviando un... (marque uno con "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

Problema de seguimiento para #12275 según lo solicitado por @DzmitryShylovich

Comportamiento actual
entryComponents de un NgModule con carga diferida no se puede representar usando ComponentFactoryResolver . Mensaje de error: No component factory found for {{entryComponent}}

Comportamiento esperado
entryComponents debería estar disponible como si el módulo fuera importado

Mínima reproducción del problema con instrucciones.
http://plnkr.co/edit/9euisYeSNbEFrfzOhhzf?p=preview

Creé una configuración simple, similar a la que usamos internamente. El componente Main proporciona un método para representar un Type . EntryComponent se declara como entryComponent en Page1Module . Sin embargo, después de haber cargado Page1Module , al intentar renderizar EntryComponent a través ComponentFactoryResolver , se lanza No component factory found for EntryComponent .

¿Cuál es la motivación/caso de uso para cambiar el comportamiento?
Renderizar entryComponents de módulos secundarios en el nivel raíz. Los casos de uso para este enfoque son

  • Modales que se representarán encima de todo
  • Notificaciones
  • etc

Cuéntanos sobre tu entorno:
Actualmente estamos usando Angular 2.1.1, pero esto también afecta a la última versión de Angular (2.4.6) (ver plnkr).

  • Idioma: mecanografiado ^ 2.0.0
core feature

Comentario más útil

Ok, en este caso estoy cambiando este problema a una solicitud de función.

En mi opinión, estos casos de uso deben ser manejados por Angular en lugar de mover las cosas a lo largo del DOM manualmente.

Todos 122 comentarios

Después de algunas investigaciones, descubrí que en su trabajo funciona según lo diseñado.
Los componentes de entrada siguen las mismas reglas que los proveedores en módulos con carga diferida. https://angular.io/docs/ts/latest/cookbook/ngmodule-faq.html#! #q-lazy-loaded-module-provider-visibility
Solo son visibles dentro del módulo de carga diferida.

Ok, si este es el comportamiento previsto, ¿cómo se pueden lograr tareas específicas como los casos de uso anteriores?

No creo que volar el módulo principal sea una solución adecuada.

Si desea utilizar un componente de entrada en MainComponent , debe proporcionarlo en AppModule

Entonces, ¿su solución es mover todo al módulo principal?

En mi opinión, esto rompe totalmente el concepto de módulo de Angular, que debería permitir agrupar bloques de funcionalidad en un solo módulo.

Esto significa básicamente que cosas como modales son imposibles de usar con una arquitectura de carga diferida.

Debe usar una API similar a la del material https://material.angular.io/components/component/dialog
Entonces debería funcionar

Wow, entonces tu opinión es que deberíamos mover contenido DOM, como lo hace Angular Material. Consulte aquí: https://github.com/angular/material2/blob/master/src/lib/core/portal/dom-portal-host.ts#L30 -L55

Si esta es realmente la mejor práctica para manejar esto en Angular, básicamente podemos volcar Angular y comenzar a usar JQuery nuevamente.

Lo siento, pero no puedo tomar este enfoque en serio.

No sé cuál es el mejor enfoque para modales y carga diferida, pero de todos modos en su plunkr funciona según lo previsto y no hay ningún error angular aquí.
Para la discusión sobre modales, es mejor usar canales de soporte como gitter y stackoverflow

Ok, en este caso estoy cambiando este problema a una solicitud de función.

En mi opinión, estos casos de uso deben ser manejados por Angular en lugar de mover las cosas a lo largo del DOM manualmente.

Enfrentado con el mismo problema hoy: el componente para modal definido en el módulo de carga diferida no es manejado por el servicio modal de la aplicación (no puede encontrarlo a pesar del uso entryComponents ). Obligado a moverlo al paquete principal que rompe la estructura de módulos perezosos y la lógica de uso: confundido:

El mismo problema allí ... Imposible usar un componente de entrada en un módulo lazyLoaded sin romper la lógica de los módulos perezosos: mi módulo LazyLoaded depende de un componente de entrada declarado en mi AppModule :(

Me encontré con este mismo problema, aunque llegué a una resolución después de revisar el código fuente modal ng-bootstrap. Esencialmente, el problema (según tengo entendido, corríjame si me equivoco) es que sus módulos perezosos no están incluidos en el inyector inicial cuando su aplicación se inicia y el inyector inicial no se puede modificar una vez que está configurado. Por eso te sale el error en el OP. Cuando se carga uno de sus módulos perezosos, obtiene su propio inyector con referencias a lo que declare o proporcione en su módulo perezoso. Dicho esto, siempre que donde sea que cree su componente dinámico tenga una referencia a los injector y componentFactoryResolver correctos, de hecho puede hacer referencia a un entryComponent fuera del módulo de carga diferida.

Entonces, lo que hice fue crear un servicio singleton para almacenar los injector y componentFactoryResolver del componente dinámico que quiero crear. Este servicio debe estar fuera de sus módulos perezosos. Luego, cada vez que voy a crear dinámicamente un componente, puedo llamar a este servicio para obtener lo que necesito.

El código a continuación estará donde esté creando su componente dinámico fuera de su módulo perezoso

@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) {}

Avíseme si esto no tiene ningún sentido y puedo dar más detalles. Editar Olvidé mencionar que estoy usando Angular 2.4.9.

Gracias @jmcclanahan. ¿Puede contarnos un poco más sobre cómo se ve ese servicio y cómo está configurado? Estoy luchando con un problema similar, intento cargar dinámicamente un componente de un módulo con carga diferida en un componente de otro módulo con carga diferida y aunque (creo) he agregado los componentes de entrada en el lugar correcto, me dicen que no lo he hecho Gracias

@gcorgnet - Claro. Así que mi caso de uso fue, tengo una barra de herramientas común en cada página de mi aplicación, pero dentro de la barra de herramientas quería agregar botones y herramientas para una funcionalidad adicional según la página en la que estaba. Para lograr esto, creé un componente para contener estos botones/herramientas y su lógica y quería mantener estos componentes dentro del módulo al que están asociados. Entonces, esencialmente nada fuera de mi módulo sabe nada sobre estas funciones específicas de la barra de herramientas mientras aún puede mostrarlas en la barra de herramientas común. A continuación se muestra mi solución de trabajo que espero te ayude:

Todo lo que estoy haciendo con el servicio de la barra de herramientas es crear un observable, por lo que su componente perezoso puede pasar sus referencias componentFactoryResolver y injector al componente común de la barra de herramientas que está escuchando el receiveContext Evento.

barra de herramientas.servicio.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 });
    }
}

En su componente de carga diferida, querrá inyectar componentFactoryResolver y injector y activar un evento en el servicio de la barra de herramientas.

componente de la barra de herramientas con carga diferida

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

Finalmente, en mi componente central de la barra de herramientas, me suscribo al observable en el servicio de la barra de herramientas para que cada vez que se active un evento, intente inyectar y crear el componente perezoso que necesito. También es importante destruir los componentRef que creas, de lo contrario terminarás con pérdidas de memoria.

...
@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();
  }

Su componente creado dinámicamente se colocará donde coloque los #toolbarTarget correspondientes

<div #toolbarTarget></div>

Aparte, estoy usando esta línea this.component = data['toolbar']; en el componente de la barra de herramientas común para obtener la referencia del componente de carga diferida de la ruta. Si desea ver el código completo para esto, puedo publicarlo, pero está fuera del alcance de esta discusión.

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

¡Déjame saber si tienes más preguntas!

@jmcclanahan Tengo una pregunta (probablemente una estúpida) sobre su código, cuando menciona el "componente de la barra de herramientas con carga diferida", coloca el código de llamada de servicio en la sección ngOnInit, pero ¿cómo logra inicializarlo? (el componente) sin tener que agregarlo al código HTML y ocultarlo luego en el DOM? Dado que ese componente solo se usará en la barra superior, no lo cargo en ningún otro lugar antes.

@Dunos : me di cuenta de que dejé esa parte por completo fuera de mi ejemplo anterior, ¡lo siento! Querrá agregar el componente dinámico como entryComponent en el módulo de carga diferida con el que está asociado.

Luego, en el componente de la barra de herramientas, definirá un ViewContainerRef que hará referencia a la ubicación en su plantilla html donde desea que se muestre el componente dinámico. Luego, el método updateToolbar() se encargará de crear su componente, colocándolo en la ubicación especificada y mostrando el componente en la barra de herramientas.

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

Su componente creado dinámicamente se colocará donde coloque el #toolbarTarget correspondiente

<div #toolbarTarget></div>

Se llama al método clearComponent() cada vez que se ejecuta ngOnDestroy(), que se ocupa de ocultar el componente cuando abandona la página.

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

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

También actualicé mi publicación original arriba. He estado recibiendo muchas preguntas sobre esto, así que agregaré un ejemplo de trabajo usando esta barra de herramientas dinámica tan pronto como pueda y los actualizaré con un enlace.

@jmcclanahan que obtengo (creo), pero todavía no entiendo cómo se realiza la llamada al servicio desde el componente dinámico (la parte de la llamada this.toolbarService.receiveContext ) ya que nunca se llama a ngOnInit (o no no veo cómo hacerlo). Agregué a los componentes de entrada, pero necesito inicializarlos un poco para que onInit funcione.

@Dunos : tal vez un ejemplo sea mejor. Digamos que estoy navegando desde la página de índice de mi aplicación a la página A. Tendrá una ruta correspondiente para la página A, que definirá el módulo/componente de la página A. La página A también tendrá un componente separado definido que deseo mostrar en la barra de herramientas global. Entonces, dentro del componente principal de la página A, inyectará ComponentFactoryResolver y Injector y luego activará un evento receiveContext . Este bit de código:

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

Lo siento, pude ver cómo eso podría ser confuso. No coloca ese código en el componente que desea mostrar en la barra de herramientas, sino en el componente asociado a la página en la que se encuentra.

@jmcclanahan Ah perfecto, muchas gracias!!! Lo tengo funcionando ahora :)

@jmcclanahan Acabo de resolver el mismo problema antes de encontrar esta publicación. Una cosa que noté es que si desea guardar algunos caracteres en las declaraciones de su función, puede obtener una referencia al ComponentFactoryResolver usando el Inyector que pasa. solo puedes hacer:
injector.get(ComponentFactoryResolver);

Y eso debería devolver la referencia que necesita. Pero al final da el mismo resultado que pasar el ComponentFactoryResolver como un parámetro en la función también.

Esto resuelve mi problema también.

En resumen, todo lo que necesita saber es que cuando desea crear dinámicamente un componente declarado en un módulo diferido, debe usar ComponentFactoryResolver de un componente declarado en ese módulo diferido. El inyector raíz (que contiene el ComponentFactoryResolver raíz) no conocerá ningún componente de entrada declarado en un módulo perezoso cargado.

Parece un poco extraño, pero así es como está diseñado Angular (lo cual tiene sentido), pero esto en realidad crea una complejidad adicional cuando se trabaja con el marco, en mi humilde opinión.

Cómo el equipo de Angular podría aprovechar esto: cada vez que se activa una ruta, el inyector raíz debe agregar los componentes de entrada enumerados en ese módulo perezoso automáticamente y cada vez que se cambia la ruta, los componentes de entrada agregados anteriormente deben destruirse automáticamente, esto garantizaría una menor complejidad y evitaría una inundación excesiva el inyector raíz con fábricas de componentes adicionales.

Enviaré una solicitud de función para esto.

¡Gracias!

Muchas gracias @jmcclanahan

@jmcclanahan muchas gracias!! @renatoaraujoc , ¿ha enviado esta función al equipo de Angular?

@rraya He enviado una solicitud de función con respecto a este "problema" (no es realmente un problema si lo piensa), consulte #17168.

Solo para agregar a esto. Mi caso de uso es que he creado un ModalManagerService en mi AppModule que tiene un método abierto que todas las demás partes de la aplicación llaman para abrir un modal. Se pasa un componente que luego se abre como un modal. También tengo un método closeAll que se usa cuando los modales deben cerrarse por alguna acción que no sea que el usuario los cierre, por ejemplo, cambio de ruta debido al tiempo de espera de la sesión.

@jmcclanahan pero todavía no entiendo el tuyo, muchas gracias si hay algunas sugerencias. Parece que ocurrió un error en 'this.componentFactoryResolver.resolveComponentFactory'

@Dunos , ¿puedes echarme una mano? Sufrí el mismo problema durante semanas y es realmente un dolor de cabeza.

@werts ,

Todo lo que necesita comprender es que siempre que navegue a una ruta con carga diferida, la ruta activada generará un inyector secundario que contiene los componentes de entrada para ese módulo perezoso. Si tiene un modalService global definido en AppModule (que es el inyector raíz de la aplicación), el componenteFactoryResolver no reconocerá ningún componente de entrada nuevo de ningún módulo perezoso. Entonces, una de las formas de resolver este problema es obtener la instancia de complementFactoryResolver de un componente de lazyModule y de alguna manera decirle al modalService "oye, usa este inyector/componentFactoryResolver para generar el componente de entrada deseado".

Parece un poco difícil de entender, pero no lo es.

@renatoaraujoc gracias por su explicación, en mi caso de uso, quiero cargar el componente desde un enlace, es decir, hago clic en el menú de la barra lateral izquierda, y recuperaré el módulo de un módulo con carga diferida, y luego crearé un componente dinámico, porque quiero crear una aplicación similar a una pestaña, por lo tanto, en el área de contenido principal derecha, hay contenido de componente, si hago clic en el activador de pestaña, el contenido se mostrará. ¿O hay alguna pestaña de creación de solución? como aplicación a través de angular2/4?

de todos modos, el componente lazyload nunca se puede iniciar, ¿cómo puedo decirle al servicio su componenteFactoryResolver?

para mi aplicación, hay un AppModule, y contiene CoreModule, por lo tanto, mi ContentTabModule (que se usa para cargar el conjunto de pestañas) pertenece a CoreModule, todos los componentes de creación son manejados por ContentTabModule, tiene servicio, componente, directiva, tubería, cuando se hace clic en un menú enlace, ContentTabService creará un componente dinámico desde los parámetros del menú. He visto algunos troncos (uso webpack) cargados en el panel de red. Y luego quiero crear un componente que se demuestre en el módulo de carga diferida.

@renatoaraujoc ¿cómo puedo obtener la instancia de complementFactoryResolver de un componente de lazyModule? Creo que es mucho más difícil, porque el componente de lazymodule nunca se inicia (o me equivoco)

@werts revisa la conversación que tuve con @jmcclanahan arriba, hablamos sobre algo similar con una barra de herramientas.

@Dunos no puedo entender el ejemplo de @jmcclanahan , creo que sufro lo mismo. El componente lazyload nunca se inicia.
this.toolbarService.receiveContext() llamado desde el módulo lazyload?

@werts , debe cargar el componente principal del módulo perezoso con las rutas, y ese componente es el que llama al servicio auxiliar con su Inyector y ComponentFactoryResolver.

@Dunos gracias, ¿pueden ayudarme si es conveniente para usted, algún otro caso de uso, quiero cargar un módulo con un clic en el enlace y luego crear un componente dinámico? ¿Es posible?

@werts ... sí, es posible. Mire https://github.com/Toxicable/module-loading para conocer el punto de partida.

ok, pruébalo ahora, gracias. @mlc-mlapis

@werts et al: ¿Puedo recomendarle que lleve este hilo de soporte a otro canal? StackOverflow o Gitter podrían ser más adecuados. Tal como están las cosas, estoy suscrito a este problema (como estoy seguro de que hay muchos otros) y está generando mucho ruido en este momento que se está desviando un poco del tema.

En el lado positivo: ¡es genial ver una comunidad tan útil!

No puedo creer que este problema sea también otro obstáculo que debo enfrentar con los módulos con carga diferida. He estado buscando desesperadamente una solución para mantener anónimos a los hijos de mi módulo perezoso de su padre (app.module) para que pueda eliminar/exportar fácilmente el módulo perezoso si es necesario. Para algo tan simple como una aplicación de ancho <router-outlet name="app-popup"> en app.component.html (al que he renunciado con la esperanza de que ComponentFactory pueda reemplazar su trabajo) solo para cargar componentes de niños perezosos ha sido muy desalentador. Solo para encontrarme ahora con el problema de los componentes de entrada con los niños de entrada de módulos perezosos. Simplemente no es posible mantener mis módulos aislados correctamente si necesito que el módulo raíz registre los componentes secundarios perezosos. Solo espero que el equipo de Angular se dé cuenta de que esto es un problema pronto y lo resuelva. Todavía estoy esperando que respondan a mi problema con una salida de enrutador para toda la aplicación para módulos perezosos. https://github.com/angular/angular/issues/19520

En el principio no había nada,
'¡Vamos a crear una vista! ', dice el equipo de jQuery,
¡Hagamos una lista! ', dice el equipo de Angular.

@jmcclanahan tenemos ngComponentOutlet ahora, hace las cosas mucho más simples.

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

@crysislinux ngComponentOutlet es excelente, pero aún no nos permite pasar datos al componente, por lo que no es una solución.

¿Alguna noticia sobre esta característica? =/

Me acabo de encontrar con este problema. Probé un par de sugerencias, sin éxito. Sin embargo, lo tengo funcionando. En realidad, resultó que a mi módulo con carga lenta le faltaba una importación de otro módulo dependiente (en este caso, el módulo modal NGX), pero el error acusó a mi componente de no poder resolverse.

Tengo curiosidad: ¿Se resuelve este problema mediante el uso de ui-router? ¿O tal vez incluso la capacidad de múltiples puntos de venta del enrutador de angular? ¿Hacen esta lógica detrás de escena? Este es un caso muy común para mí en angular 1.x y estoy tratando de hacer la transición a angular 2+

En el ejemplo de la barra de herramientas, la vista de interfaz de usuario (probablemente una vista con nombre o salida de enrutador en el caso de un enrutador angular) se configuraría en un módulo de nivel superior. Si un estado de módulo con carga diferida reemplaza la vista de interfaz de usuario de la barra de herramientas con su propio contenido, tendría que simplemente "funcionar" o el enrutador de interfaz de usuario no funcionaría en un entorno de carga diferida donde las vistas de nivel superior fuera del módulo actual deben ser reemplazadas . Eso sería extremadamente limitante en comparación con angular 1.x.

Parece que el ejemplo anterior está resolviendo lo mismo que ui-router/angular router está diseñado para resolver. ¿Me estoy perdiendo de algo?

... probablemente estemos perdidos cuál es el problema en absoluto. Como sabemos y usamos todos los días, no hay problema para usar componentes de módulos con carga diferida ... incluso a través de un enrutador o programáticamente por nuestro propio código.

Cuando uso el ejemplo anterior de @jmcclanahan , parece funcionar bien para mi caso de uso.

Tuve que agregar this.componentRef.changeDetectorRef.markForCheck(); después this.componentRef = this.toolbarTarget.createComponent(childComponent, 0, this.refInjector);.

Esto solucionó un problema que tenía con las propiedades de HostBinding que no se activaban.

Pensé que probaría una posible solución que me ha funcionado. Esta es una bifurcación del plunkr original.
http://plnkr.co/edit/x3fdwqrFDQr2og0p6gwr?p=vista previa

Para evitar esto, debe resolver la fábrica de componentes desde un servicio (ResolverService) provisto en el módulo que tiene el componente de entrada (Page1Module). Esto luego se pasa a un segundo servicio (ChangerService) que usa la fábrica resuelta y agrega el componente a la vista en MainComponent.

Lo que realmente no entiendo es este maldito enrutador. Verifique este plunker (bifurcado del ejemplo de @patkoeller )
http://plnkr.co/edit/keMuCdU9BwBDNcaMUAEU?p=vista previa @ src/page1.ts

Básicamente, lo que quiero hacer es inyectar algún componente en la aplicación de nivel superior y usar la directiva [routerLink] con comandos relativos a su módulo de carga diferida definidor como: [routerLink]="['./', 'sub', 'component'] para generar /page-1/sub/component . Si le paso un inyector a createComponent con unos ActivatedRoute y funciona para mi caso, arruina todos los demás routerLink, pero si reasigno router.routerState.snapshot a los state de la resolución y pase el inyector con esta instancia de enrutador, parece que funciona según lo previsto... ¡muy confuso!

¿Debo presentar un error?

Me enfrento al mismo problema con los componentes de entrada para mi servicio de diálogo único. Los componentes modales relacionados con funciones deben permanecer en su módulo respectivo sin la necesidad de la solución alternativa de @jmcclanahan .

¿Alguna nueva ETA sobre esto? ¿O al menos una respuesta del equipo de Angular? No tener esta funcionalidad es una verdadera decepción ya que la encapsulación del módulo se acaba de tirar por la ventana. Todos los cuadros de diálogo para @angular/material deben importarse en el módulo raíz.

@ xban1x No creo que tengamos ningún progreso con eso. Tenemos que hacer el recorrido de la misma :(

@jmcclanahan , ¿es posible obtener una referencia del componente inyectado utilizando su enfoque? Inyecté con éxito un componente, es un árbol por cierto, así que ahora necesito suscribirme a los eventos de clic de árbol en el componente que inyectó el árbol. ¿Hay alguna manera de lograr esto?

En el problema #23819 pregunté sobre cómo hacer que los componentes de entrada se comporten de manera similar a los servicios con providesIn: 'root' . Cerré ese problema porque parece que ambos problemas están destinados a resolver lo mismo. Espero que este problema se resuelva pronto porque esta es una característica importante :)

Actualizar

Por lo que veo en el código no minificado, cuando se define providesIn en el servicio, da de alta el factory para que ya se pueda usar (sin necesidad de importarlo en un módulo), si entendí correctamente:

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

Supongo que hay una forma de aplicar una lógica similar en un componente con alguna opción como @Component({ ... entryComponent: true }) para que cuando se cargue el fragmento que lo contiene, la fábrica se defina en el resolver (debe haber un módulo que incluya el componente en entryComponents con las dependencias del componente, de lo contrario provocaría un error de tiempo de compilación).

@lucasbasquotto ... usamos la siguiente lógica para cargar un módulo y usar sus tipos de componentes (con nuestro propio mapeo entre claves de cadena <-> tipos de componentes almacenados directamente en ese módulo):

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, []);
    });

Hola chicos, por ahora también estoy usando la solución alternativa de tener una clase singleton que mantiene una referencia a los varios solucionadores de módulos con carga diferida.
¿Hay alguna forma mejor de manejar esto con la nueva versión de Angular?
Gracias

Hola a todos,

Solo enfrento el mismo problema y uso la solución alternativa como un arranque para mi solución.

Una forma de ir más allá y reducir la necesidad de tener un servicio global sería exponer la variable privada loadedConfig del evento RouteConfigLoadEnd. Esta variable privada de tipo LoadedRouterConfig contiene el módulo cargado, su inyector y su componenteFactoryResolver.

¿Debería abrir una nueva edición para proponer esto como una solicitud de función?
--------- Después de editar

No importa mi pregunta, encontré esta solicitud de función https://github.com/angular/angular/issues/24069

Creo que tengo el mismo problema, pero mi módulo adicional no es lazy loaded . Tengo que mover mi entryModules de SharedFeatureModule (que se carga solo si es necesario) a CoreModule .
Ahora funciona. Pero moví el componente relacionado con la función al núcleo, lo que no está bien en mi opinión.

¿Este problema aún no está resuelto?

La solución de @jmcclanahan

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

Agrega una ruta en blanco adicional al final. Cuando trato de crear una ruta de navegación dinámica, esto se está convirtiendo en un problema.

Sin agregar esta ruta en blanco por defecto al componente, prefiero tener algo similar a bootstrap en módulos secundarios perezosos.

¿Alguna actualización sobre esto?

Nuestra solución personalizada implica que el constructor inyecte ComponentFactoryResolver y Injector en cada componente que podría abrir nuestra barra lateral global. El componente que llama luego pasa esas referencias (propias) a un servicio junto con el Component deseado. El servicio usa esas instancias para crear el componente. Demasiado complicado y demasiado detallado.

Simplemente coloque sus componentes de entrada en su propio submódulo e impórtelo en la aplicación y en ningún otro lugar.

@broweratcognitecdotcom Creo que el punto de este problema es para entryComponents en módulos con carga diferida . Incluir todos los submódulos de cada componente que desee usar como componente de entrada en AppModule forzará que todos esos componentes se carguen con entusiasmo, lo que podría causar problemas de rendimiento al inicio .

No quiere decir que esos componentes deban ser agnósticos a los componentes que los usan (los consumidores), de tal forma que sus módulos no deben saber si están siendo usados ​​como componentes de entrada o como componentes normales (declarados en el html), solo su consumidor debería saberlo (por lo tanto, ponerlos en AppModule dificultaría saber por qué están allí, tendría que buscar en otros lugares para saberlo).

@lucasbasquotto ... carga esos módulos perezosos programáticamente... entonces tienes el control total, y simplemente funciona.

Me topé con este hilo después de encontrarme con un problema extraño en el que no podía resolver el componente desde la resolución raíz mediante el uso de un mapa de claves de cadena y los componentes correspondientes. Literalmente puedo ver el componente en el mapa _factories pero de alguna manera no se compara correctamente y devuelve undefined.

@jmcclanahan @Dunos ¿No cree que en lugar de que el módulo de funciones entregue su referencia de inyector (registrar su contexto con el módulo de la aplicación principal) será mejor si expone un servicio, digamos FactoryResolver, que en realidad devolverá la fábrica resuelta correctamente para su componentes ya que tendrá referencia correcta al inyector de su propio módulo. De esta forma, será como exponer una API pública para sus componentes.

Estoy tratando de hacer algo similar, pero mi módulo no está cargado de forma diferida y me encuentro con el problema que describí anteriormente. En teoría, debería funcionar, a menos que me esté perdiendo algo obvio aquí.

@ h-arora Noté el mismo problema al ver la composición. en _factories, pero el problema era que el módulo tenía un error de compilación, que no apareció

Coping over from #17168 (cerrado como un duplicado de este)

Comportamiento actual
Si tiene un servicio singleton que utiliza un ComponentFactoryResolver proporcionado por RootInjector de la aplicación para crear componentes dinámicamente y desea crear un componente de entrada declarado en un módulo perezoso, esto fallará porque el inyector raíz no lo sabe.

Comportamiento esperado
Este es en realidad el comportamiento esperado considerando que el inyector raíz no conocerá los componentes de entrada conocidos por el inyector secundario.

¿Cuál es la motivación/caso de uso para cambiar el comportamiento?
La motivación es aprovechar la complejidad para crear componentes dinámicos, aunque el diseño modular de Angular es correcto.

Versión angular: 2.4.9

Solución propuesta: aunque esto va en contra del diseño modular adoptado por Angular, creo que la complejidad proporcionada para crear componentes dinámicos podría suavizarse simplemente agregando los componentes de entrada de LazyModule a los componentes de entrada de RootInjector y para evitar inundar RootInjector, cada vez que navega fuera (que destruye el LazyModule), los componentes de entrada previamente inyectados se borrarían de la matriz.

Simplemente hablando:

Se carga la página -> Se crea RootInjector.
Emite una navegación que activa un LazyModule -> Se crea ChildInjector.
RootInjector.entryComponents.append(childInjector.entryComponents)
Emite una navegación a otro lugar -> LazyModule ya no es necesario, por lo tanto, se destruye.
RootInjector.entryComponents.destroyLastAppendedEntryComponents()
Espero poder sugerir algo bueno.

Pasé un par de días separando el proyecto de mi empresa e implementando la carga diferida. No fue hasta el final que me di cuenta de que nuestros modales no funcionaban debido a que eran entradas en sus respectivos módulos. Hemos retrocedido y estamos esperando una solución...

¿Es esto algo que está programado o deberíamos explorar una solución? Como la gente está haciendo arriba.

@ravtakhar , probablemente desee ver la solución, ya que está en el Backlog y no tiene una prioridad asignada en este momento.

Pasé un par de días separando el proyecto de mi empresa e implementando la carga diferida. No fue hasta el final que me di cuenta de que nuestros modales no funcionaban debido a que eran entradas en sus respectivos módulos. Hemos retrocedido y estamos esperando una solución...

¿Es esto algo que está programado o deberíamos explorar una solución? Como la gente está haciendo arriba.

El mismo problema aqui

@tmirun ... si carga módulos tan perezosos mediante programación ... entonces tiene el control total ... significa que finalmente puede obtener una referencia de módulo y llamar a moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponentType)

Tuve el mismo problema que muchos anteriores, y lo resolví creando un módulo independiente que declaraba y exportaba todos los componentes de entrada, incluido el del appModule y también lo exportaba, de modo que cada módulo lazyLoaded cargará estos componentes.

La única otra solución factible que encontré también fue generar cuadros de diálogo al proporcionar un ComponentFactoryResolver personalizado de acuerdo con el módulo que aloja el entryComponent .

CUIDADO : recuerde que los módulos singleton proporcionados en la raíz solo verán los componentes de entrada del módulo principal , es por eso que he decidido encontrar la primera solución; de lo contrario, puede pensar en crear un generador de diálogo para cada submódulo de su aplicación y, en ese submódulo, eventualmente incluye módulos compartidos que tienen otros entryComponents , proporcionando al servicio que genera los modales los ComponentFactoryResolver de dichos módulos.

... la solución compatible general debería estar disponible (algunas nuevas API + soporte en Ivy) ... debido a la presentación de Jason http://www.youtube.com/watch?v=2wMQTxtpvoY&t=131m24s ... en Angular 7.2. .. pero no estoy seguro si la versión 7.2 fue mencionada correctamente.

Tengo el mismo problema con la superposición de CDK , tengo un complemento de galería que se construye sobre la superposición de CDK, pero debido a este problema, tengo que importarlo en el módulo raíz.

Por cierto, ¿hay alguna solución para mi caso?

@MurhafSousli ... lol, tengo que decir nuevamente que es por diseño. La cosa se explicó cien veces... hay un inyector raíz y cualquier módulo con carga diferida tiene un nivel de profundidad, por lo que no puede suponer que puede tocar sus componentes desde el nivel raíz. Pero como se dijo muchas veces, puede cargar ese módulo mediante programación... de modo que obtendrá la referencia de ese módulo... y podrá acceder a sus componentes en ese momento.

Agrégame a la lista también 😄. Tengo un componente modal donde cargo mis componentes dinámicamente y no quiero cargarlos todos en la aplicación, lo que sería un mal diseño ya que no son parte del componente de la aplicación. Pero eso es lo que tengo que hacer, supongo, después de leer todo esto.

@msarica me señaló hoy que si crea un servicio que se carga de forma diferida y hace que amplíe su servicio global que abre ventanas emergentes, luego use el servicio de carga diferida para abrir la ventana emergente, funcionará como se esperaba:

Global PopupService

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

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

Servicio específico solo cargado de forma diferida en MyFeatureModule

@Injectable()
export class MyFeaturePopupService extends PopupService {}

Uso en un componente que forma parte de MyFeatureModule carga diferida

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

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

Definición de ejemplo de módulo cargado perezosamente

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

Probado en Angular 7.

Nota: Nuestro código aprovecha el Overlay Angular CDK, pero el concepto es el mismo (según otras partes de este hilo, lo que preocupa es el Injector ).

... la solución compatible general debería estar disponible (algunas nuevas API + soporte en Ivy) ... en Angular 7.2

... @mlc-mlapis, ¿podría ampliar cuál sería la solución compatible general?

@cedvdb ... Acabo de mencionar el momento importante en esa presentación que en realidad parece una solución ... significa la capacidad admitida de cargar mediante programación un módulo con carga diferida y luego usar cualquier parte de él, como un componente, una directiva, ...

Esa forma está disponible incluso hoy... pero con Angular CLI siempre necesita definir las rutas necesarias... incluso si nunca las usa a través del enrutador.

Guau. Esto fue muy inesperado.

Implementé mi propio sistema de diálogo basado en portales y superposiciones de @angular/cdk: https://stackblitz.com/edit/cdk-dialog-example-p1. Tengo este problema también.

La solución mencionada por @CWSpear funciona: tuve que extender el servicio en mi módulo de funciones y funcionó para mí. ¡Que raro!

¿Hay una solución más elegante para esto?

¿Hay una solución más elegante para esto?

Algo con lo que he estado experimentando es volver a proporcionar un contenedor de inicio en el nivel del módulo perezoso. Haga que este contenedor tome un inyector en el constructor y lo pase al lanzador real (que se inyecta desde la raíz). Es una solución de una línea. Sin embargo, no estoy seguro de cuán estable es,

@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) {
    }

Mi problema se debió a que usé @Injectable({providedIn: 'root'}) . Cambiándolo a @Injectable({providedIn: MyModule}) lo resolvió. 😄

Esto tiene sentido, porque el servicio necesita acceso a los componentes de entrada de los módulos con carga diferida. Cuando se proporciona en la raíz, no puede acceder a ellos porque el módulo está temporalmente desconectado del inyector raíz.

Muchas gracias a @jmcclanahan y @dirkluijk , estuve rascándome la cabeza durante horas y su descripción ayudó a encontrar una solución para mi caso.

Solo me pregunto cuál es la forma correcta de hacerlo.
¿Se necesita @angular/cdk? Por qué ?

No creo que ninguna solución alternativa sea viable si requiere que el código con el que se está integrando lo sepa, porque hay muchos códigos de terceros que solo usan ComponentFactoryResolver sin conocer su registro alternativo especial.

Con eso en mente, aquí está mi "solución": CoalescingComponentFactoryResolver . Este es un servicio que debe ser proporcionado por el módulo de la aplicación e inicializado en su constructor, así:

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

Luego, los módulos con carga diferida deberían inyectarlo y registrar sus propias instancias ComponentFactoryResolver con él. Al igual que:

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

Cuando se hace esto, los componentes de entrada en el módulo de carga diferida deben estar disponibles desde los servicios de carga no diferida.

Cómo funciona: cuando se inicializa, inyecta ComponentFactoryResolver de la aplicación raíz y parchea el método resolveComponentFactory para llamar a su propia implementación. Esta implementación primero intenta resolver la fábrica de componentes en todos los resolutores de módulos perezosos registrados, luego recurre al resolutor de la aplicación raíz (hay un poco de lógica adicional para evitar llamadas cíclicas, pero esa es la esencia).

Entonces, sí, un truco bastante asqueroso. Pero funciona, ahora mismo, en Angular 7. Tal vez le sirva a alguien.

Sobre la base de la solución de @CWSpear , conseguí que esto funcionara simplemente agregando mi PopupService existente a la matriz de proveedores del componente. Por lo tanto, no necesitaba crear un PopupService separado "específico de la función".

Así que así:

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

Alternativamente, también puede agregar PopupService a la matriz de proveedores del NgModule del módulo de carga diferida. P.ej:

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

@jonrimmer mejor solución

@jonrimmer Esto es mínimamente intrusivo y funciona perfectamente para nuestro caso de uso. Un enorme agradecimiento de mi parte y de mi equipo :)

Tengo curiosidad por saber cómo funciona sin problemas en el caso de un enrutador perezoso, pero arroja un error cuando eliminé la ruta perezosa y cargué los módulos manualmente usando 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 Esto es para el modo JIT. Debe cargar import("../bar/bar.module.ngfactory") para el modo AOT (donde compileModuleAndAllComponentsSync no está disponible).

@mlc-mlapis
fuertemente inspirado en https://github.com/angular/angular/blob/master/aio/src/app/custom-elements/elements-loader.ts

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

Puede usar AOT JIT. exito para mi

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

```mecanografiado
import { Compiler, Inject, Injectable, NgModuleFactory, NgModuleRef, Type, ComponentFactory } from '@angular/core';
importar {LoadChildrenCallback} desde '@angular/router';

importar { ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN} desde './lazy-component-registry';

@Inyectable({
proporcionado en: 'raíz'
})
exportar clase LazyComponentLoader {
módulos privados para cargar: mapa;
módulos privados Cargando = nuevo Mapa>();

constructor(
módulo privadoRef: NgModuleRef,
@Inject(ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN) elementModulePaths: Mapa,
compilador privado: Compilador
) {
this.modulesToLoad = new Map(elementModulePaths);
}

load(ruta: cadena, selector?: cadena): Promesa{
if (this.modulesLoading.has(ruta)) {
devuelve this.modulesLoading.get(ruta);
}

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 ... entonces correcto. Está usando el compilador solo en modo JIT.

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

@mlc-mlapis AOT será NgModuleFactory. Puede probar https://github.com/Jimmysh/ng-lazy-component-load

Hola chicos, con la ayuda de los comentarios de este hilo, he creado un paquete que pretende ser una solución alternativa para intentar que los componentes de entrada se carguen fuera del módulo a través del enrutamiento. Por favor, échale un vistazo y espero que te ayude.

Enlace de repositorio: https://github.com/Jonathan002/route-master-example
URL de demostración: https://jonathan002.github.io/route-master-example/

FWIW, en una línea similar a @CWSpear y @dirkluijk , eliminé el providedIn: 'root' de mi servicio (definido en un módulo con carga diferida y responsable de abrir un entryComponent ) y en su lugar especifiqué el servicio en el providers de mi definición de módulo perezoso. Creo que el mayor/único inconveniente de esto es que el servicio ya no se puede sacudir.

El uso providedIn: MyLazyModule generó advertencias de dependencias circulares.

@jonrimmer ¡No podía dejar el hilo sin agradecerte! Salvaste mi día.
Para todos los que lean mi comentario, puede ayudarlos a no perder el tiempo si miran esta maravillosa respuesta .

Confirmado, esto aún no se resuelve en Angular 8.2.0. Desde que se creó el OP hace 2,5 años, el panorama de Angular había cambiado mucho y Angular Material 2 se había convertido en Angular Components.

Como indicaron los otros comentaristas, Angular Material usa DOM para que los desarrolladores de aplicaciones no tengan que usar DOM al cargar un componente de aplicación en MatDialog, incluso si el componente de aplicación se declara en la entrada Componentes del submódulo cargado de forma diferida.

Sin embargo, hacer las mismas cosas DOM en una aplicación comercial parece extraño, en contra de las pautas de programación de aplicaciones de Angular.

Será bueno que la solicitud de función del OP pueda implementarse para que ComponentFactoryResolver pueda "ver" un componente de entrada declarado en un submódulo cargado de forma perezosa, de una forma u otra. Declarar un componente de entrada de un módulo perezoso en app.module funciona, pero es feo.

Además de las soluciones de trabajo sucio como se mencionó anteriormente: 1. DOM, 2. Declarando el componente en app.module, proporcionaría otra solución, menos sucia, a continuación.

la tercera solución, menos sucia.

Tengo servicios compartidos utilizados por varios módulos cargados de forma lenta.

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

    }

}

Este servicio puede ver los componentes de entrada de los módulos que no están cargados de forma diferida. Tengo algunos componentes de entrada implementados en algunos módulos cargados de forma diferida. Así que he derivado la clase de este servicio para que se proporcione en cada módulo perezoso:

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

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

Dado que LazyComponentDialogService se proporciona en el mismo módulo diferido con el componente de entrada diferida, puede ver el componente.

Dependiendo de la arquitectura de su aplicación, es posible que no necesite la clase derivada, sino que solo proporcione DataComponentDialogService o similar en cada módulo.

Parece que el problema ya no existe en Angular 9 debido a la implementación de ivy (probado con 9.0.0-next.7). Puede crear dinámicamente un componente de un módulo con carga diferida desde fuera del módulo diferido. Parece ser que la declaración del componente de entrada ya no es necesaria para los componentes dinámicos en Ivy. Ivy no necesita una fábrica de componentes separada que solía generar el compilador. La información de fábrica del componente está incrustada en la definición del componente con Ivy(?). Una vez que tenga un tipo de componente, debería poder obtener su fábrica de componentes y luego crear una instancia de él. Esa es solo mi impresión. Toma mi palabra con un grano de sal

Todo lo que necesita hacer es crear un módulo de widgets separado que declare todos sus componentes dinámicos y también los incluya como componentes de entrada. Luego importe este módulo desde AppModule. Esto asegurará que sus componentes de entrada estén registrados con el inyector raíz.

Esta solución es limpia y no rompe la encapsulación Lazy.

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

}

Módulo de aplicación:

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

@pixelbits-mk Eso haría que todos los componentes dinámicos (como modal y similares) estuvieran ansiosos por cargar , pero el punto aquí es que puedan ser cargados de forma diferida para no ralentizar la carga inicial de la aplicación (especialmente a medida que el proyecto crece ), al igual que los componentes que los llaman.

Digamos que un ComponentA carga diferida llama dinámicamente a un ComponentB dinámico con carga diferida. Si ComponentA sabe que llamará a ComponentB , puede importar ComponentBModule en ComponentAModule . Este no es el caso ideal, pero puedo vivir con ello.

El principal problema surge (en mi opinión) cuando ComponentA no sabe que se cargará ComponentB . Por ejemplo, si ComponentB es un modal y ComponentA llama a un método showModal() en un servicio, y el servicio carga ComponentB dinámicamente entre bastidores, ComponentA no sabría eso.

Lo que hago ahora es incluir la referencia ComponentB como parámetro en el método, como showModal(ComponentB) , de tal manera que ahora ComponentA sabe que el ComponentB se cargará, pero eso es malo en mi opinión (cada llamada al método debe pasar la referencia del componente).

Si los componentes de entrada estuvieran disponibles sin tener que importar sus módulos explícitamente (de lo que creo que se trata este problema), eso lo resolvería (tal vez Angular podría tener una forma de detectar que ComponentA llama a un servicio que importa ComponentB y hacer que ComponentAModule importe implícitamente ComponentBModule , y aunque podría tener el efecto no deseado de importar también módulos de componentes cuyas referencias se importan pero el motivo de la importación no es crearlas dinámicamente , vería esto más como un caso límite, y estaría bien con eso).

No he probado (todavía) la sugerencia de @jonrimmer , pero parece prometedora.

Sería increíble si ComponentA cargara automáticamente el módulo con ComponentB solo si se muestra ComponentA :
ModuleA tiene el componente ComponentA1 , ComponentA2 .
ComponentA1 referencia en html ComponentB (en el módulo ModuleB ), pero ComponentA2 no.
ModuleB sería una carga diferida automática solo si se muestra ComponentA1 , pero no si se muestra ComponentA2 .

Esto encajaría perfectamente en el escenario de los paneles en la página de inicio. Los tableros provienen de muchos módulos diferentes, pero si el tablero no está incluido (digamos a través de la configuración del usuario), no es necesario descargar ese módulo.

@jonrimmer Gran idea para envolver la función resolveComponentFactory del ComponentFactoryResolver principal y buscar los módulos de carga lenta registrados antes.

Funciona con componentes de entrada que inyectan servicios que se proporcionan en la raíz. Pero no puedo inyectar un servicio que solo se proporciona en el módulo de carga diferida. En este caso obtuve un StaticInjectorError. ¿Puedes comprobarlo?

Quería confirmar que ivy parece encargarse de este problema. Me topé con este problema hoy cuando reescribí mis módulos para que se cargaran de forma diferida y contenían componentes de entrada y servicios compartidos para instanciarlos solo para encontrar un nuevo error que indicaba que no se podía encontrar la fábrica de componentes. De hecho, me sorprendió leer que esto era un problema en primer lugar.

En cualquier caso, acabo de habilitar ivy en mi proyecto angular 8.2 existente y todo parece funcionar como se esperaba.

Mi solución para usar Overlay en módulos cargados de forma diferida:
Tienes que pasar la resolución de fábrica al constructor de ComponentPortal de esta manera

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

pero también debe proporcionar OverlayService en el módulo de carga diferida. Es necesario que OverlayService inyecte ComponentFactoryResolver del módulo en el que se proporciona OverlayService

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

@mixrich Este enfoque funcionará [en mi aplicación, estoy haciendo eso y funciona bien hasta que necesito cerrar todos los cuadros de diálogo modales en algún evento como cerrar sesión] pero tenga en cuenta que esto instanciará OverlayService cada vez que otro módulo importa este módulo, es decir, la aplicación no tendrá una sola instancia como solían ser los servicios.

Debido a que no habrá señal de servicio de superposición, NO será fácil saber cuántos cuadros de diálogo modales se abren.

Por supuesto, son los requisitos de la aplicación, pero solo hay que tener cuidado con este enfoque.

@mixrich Este enfoque funcionará [en mi aplicación, estoy haciendo eso y funciona bien hasta que necesito cerrar todos los cuadros de diálogo modales en algún evento como cerrar sesión] pero tenga en cuenta que esto instanciará OverlayService cada vez que otro módulo importa este módulo, es decir, la aplicación no tendrá una sola instancia como solían ser los servicios.

Debido a que no habrá señal de servicio de superposición, NO será fácil saber cuántos cuadros de diálogo modales se abren.

Por supuesto, son los requisitos de la aplicación, pero solo hay que tener cuidado con este enfoque.

Por ejemplo, en mi caso tengo ModalDialogModule con OverlayService proporcionado y cuando llamo a OverlayService.open(SomeComponent) también crea para mí la plantilla de ventana modal, inserta SomeComponent dentro it y devuelve una estructura de datos con observables útiles (para eventos de cierre y éxito), instancia de componente y método manual close .
Entonces, cuando necesito usar modales en mi LazyModule, solo necesito importar ModalDialogModule para obtener la capacidad de usar OverlayService . Encontré este enfoque conveniente, porque siempre sabes que para usar modal tienes que importar ModalDialogModule , como siempre sabes que para usar formas reactivas tienes que importar ReactiveFormModule

Tengo esto funcionando en Angular 8.3.6. Tengo un módulo de biblioteca con componentes de entrada (diálogos de tapete) que no se cargarán si los agrego a los componentes de entrada de los módulos de biblioteca. Dice que no puedo encontrar la fábrica de componentes para MyCustomDialog en el registro de la consola cuando traté de abrirlo.

La solución es crear un método estático en la clase NgModule del módulo de biblioteca que devuelva una matriz de todos los componentes de entrada del módulo. Luego llame a este método desde los módulos de la aplicación 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 {
}

Sé que esto todavía no soluciona los componentes de entrada en la biblioteca que no funcionan, pero al menos con el método estático, el módulo de la aplicación no tiene que conocer los componentes de entrada en la biblioteca personalizada. Simplemente llama a ese método y obtiene lo que aparece en la biblioteca. La aplicación no tiene la responsabilidad de saber qué componentes de la biblioteca son componentes de entrada.

@ asmith2306 ¿por qué no usa el operador de propagación? se ve feo

@ asmith2306 ¿por qué no usa el operador de propagación? se ve feo

Porque no funciona con eso. Hay un error al respecto en alguna parte.

parece que no funciona con el modo --prod

Incluso si funcionó, no aborda el problema REAL. el problema es que si agrego un componente a los componentes de entrada de un NgModule, y ese módulo se importa a un módulo cargado de forma diferida en mi aplicación, entonces los componentes de entrada deberían registrarse automáticamente con los componentes de entrada de las aplicaciones. No debería haber más trabajo requerido. Además, diría que cualquier componente en los componentes de entrada de cualquier NgModule debería terminar automáticamente en los componentes de entrada del módulo de la aplicación. Este no es el caso actualmente. Si lo fuera, no estaríamos aquí.

@broweratcognitecdotcom ¿Qué pasa con los conflictos y prioridades de diferentes niveles de inyección?

En mi opinión, el pedido de @ mlc-mlapis debe ser por orden de llegada.

@ mlc-mlapis ¿Quiere decir si diferentes componentes tienen el mismo nombre? Primera coincidencia o error si hay varias coincidencias. Podría vivir con el requisito de que los nombres de los componentes de entrada deben ser únicos.

@broweratcognitecdotcom Hola, sí. Porque hay que cubrir todas las variantes si hablamos de principios generales.

@ mlc-mlapis Creo que la limitación en angular que a muchos les gustaría superar es que los componentes de entrada solo pueden ser instanciados y colocados en el dom por la aplicación. La aplicación no necesita saber acerca de un cuadro de diálogo que utiliza un módulo que importo a un módulo con carga diferida. El concepto actual de componentes de entrada rompe el patrón modular de los angulares.

AFAIK con Ivy entryComponents ya no serán necesarios, ¿verdad? Dado que los componentes tendrán localidad, ¿simplemente importarlos dinámicamente siempre funcionará?

@Airblader Tienes razón, lo sabemos. Era solo un pequeño recuerdo de la historia. 😄

Para aquellos que todavía tienen problemas con estos problemas, aquí está la solución que realmente funciona: https://github.com/angular/angular/issues/14324#issuecomment -481898762

Publico un paquete para estos temas. alguna copia de código de jonrimmer.
Parece que el problema no existe en Angular 9. Si necesita solucionarlo ahora. puedes usar @aiao/lazy-component
código de muestra aquí
https://github.com/aiao-io/aiao/tree/master/integration/lazy-component

Creo que esto está solucionado en Angular v9 ejecutando ivy. Si alguien todavía enfrenta problemas similares, abra un nuevo problema con un escenario de reproducción mínima. ¡Gracias!

Este problema se ha bloqueado automáticamente debido a la inactividad.
Presente un nuevo problema si encuentra un problema similar o relacionado.

Obtenga más información sobre nuestra política de bloqueo automático de conversaciones .

_Esta acción ha sido realizada automáticamente por un bot._

¿Fue útil esta página
0 / 5 - 0 calificaciones