Angular: Componentes de entrada de um NgModule com carregamento lento não estão disponíveis fora do módulo

Criado em 6 fev. 2017  ·  122Comentários  ·  Fonte: angular/angular

Estou enviando um ... (marque um com "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 acompanhamento para #12275, conforme solicitado por @DzmitryShylovich

Comportamento atual
entryComponents de um NgModule carregado com preguiça não pode ser renderizado usando ComponentFactoryResolver . Mensagem de erro: No component factory found for {{entryComponent}}

Comportamento esperado
entryComponents deve estar disponível como se o módulo fosse importado

Reprodução mínima do problema com instruções
http://plnkr.co/edit/9euisYeSNbEFrfzOhhzf?p=preview

Criei uma configuração simples, parecida com a que usamos internamente. O componente Main fornece um método para renderizar um Type . EntryComponent é declarado como entryComponent em Page1Module . No entanto, após ter carregado Page1Module , ao tentar renderizar EntryComponent via ComponentFactoryResolver , No component factory found for EntryComponent é lançado.

Qual é a motivação/caso de uso para mudar o comportamento?
Renderize entryComponents de módulos filhos no nível raiz. Os casos de uso para esta abordagem são

  • Modais que devem ser renderizados em cima de tudo
  • Notificações
  • etc.

Por favor, conte-nos sobre o seu ambiente:
Atualmente, estamos usando o Angular 2.1.1, mas isso também afeta a versão mais recente do Angular (2.4.6) (consulte plnkr).

  • Idioma: TypeScript ^2.0.0
core feature

Comentários muito úteis

Ok, neste caso estou transformando este problema em uma solicitação de recurso.

IMO, esses casos de uso devem ser tratados pelo próprio Angular, em vez de mover as coisas ao longo do DOM manualmente.

Todos 122 comentários

Depois de alguma investigação, descobri que em seu trabalho como projetado.
Os componentes de entrada seguem as mesmas regras que os provedores em módulos carregados lentamente. https://angular.io/docs/ts/latest/cookbook/ngmodule-faq.html#! #q-lazy-loaded-module-provider-visibility
Eles são visíveis apenas dentro do módulo carregado com preguiça.

Ok, se este é o comportamento pretendido, como tarefas específicas como os casos de uso acima podem ser realizadas?

Eu não acho que explodir o módulo principal seja uma solução adequada.

Se você quiser usar um componente de entrada em MainComponent , então você deve fornecê-lo em AppModule

Então sua solução realmente é mover tudo para o módulo principal?

IMO isso quebra totalmente o conceito de módulo do Angular que deve permitir agrupar blocos de funcionalidade em um módulo.

Isso significa basicamente que coisas como modais são impossíveis de usar com uma arquitetura de carga lenta.

Você precisa usar uma API semelhante a https://material.angular.io/components/component/dialog do material
Então deve funcionar

Uau, então sua opinião é que devemos mover o conteúdo DOM, como o Angular Material faz. Veja aqui: https://github.com/angular/material2/blob/master/src/lib/core/portal/dom-portal-host.ts#L30 -L55

Se esta é realmente a melhor prática para lidar com isso em Angular, basicamente podemos despejar Angular e começar a usar JQuery novamente.

Desculpe, mas não posso levar essa abordagem a sério.

Não sei qual é a melhor abordagem para modais e carregamento lento, mas de qualquer maneira no seu plunkr funciona como pretendido e não há bug angular aqui.
Para discussão sobre modais, é melhor usar canais de suporte como gitter e stackoverflow

Ok, neste caso estou transformando este problema em uma solicitação de recurso.

IMO, esses casos de uso devem ser tratados pelo próprio Angular, em vez de mover as coisas ao longo do DOM manualmente.

Confrontado com o mesmo problema hoje - componente para modal definido no módulo carregado com preguiça não é tratado pelo serviço modal do aplicativo (ele não pode encontrá-lo apesar do uso de entryComponents ). Forçado a movê-lo para o pacote principal que quebra a estrutura dos módulos preguiçosos e a lógica de uso :confused:

Mesmo problema lá ... Impossível usar um entryComponents em um módulo lazyLoaded sem quebrar a lógica dos módulos lazy : Meu módulo LazyLoaded depende de um entryComponent declarado no meu AppModule :(

Encontrei esse mesmo problema, mas cheguei a uma resolução depois de examinar o código-fonte modal ng-bootstrap. Essencialmente, o problema (do meu entendimento, corrija-me se estiver errado) é que seus módulos preguiçosos não estão incluídos no injetor inicial quando seu aplicativo é inicializado e o injetor inicial não pode ser modificado depois de definido. É por isso que você recebe o erro no OP. Quando um de seus módulos preguiçosos é carregado, ele obtém seu próprio injetor com referências a tudo o que você declara ou fornece em seu módulo preguiçoso. Dito isto, contanto que onde quer que você crie seu componente dinâmico tenha referência ao injector correto e componentFactoryResolver você pode realmente referenciar um entryComponent fora do módulo carregado com preguiça.

Então, o que eu fiz foi criar um serviço singleton para armazenar os injector e componentFactoryResolver do componente dinâmico que eu quero criar. Este serviço precisa estar fora de seus módulos preguiçosos. Então, sempre que eu for criar um componente dinamicamente, posso chamar esse serviço para obter o que preciso.

O código abaixo estará onde você estiver criando seu componente dinâmico fora do seu módulo preguiçoso

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

Deixe-me saber se isso não faz sentido e eu posso elaborar mais. Edit Esqueci de mencionar que estou usando o Angular 2.4.9.

Obrigado @jmcclanahan. Você pode nos contar um pouco mais sobre como é esse serviço e como ele é configurado? Estou lutando com um problema semelhante, tentando carregar dinamicamente um componente de um módulo carregado lentamente em um componente de outro módulo carregado lentamente e, embora (acho) eu tenha adicionado os componentes de entrada no lugar certo, me disseram que não tenho. Obrigado

@gcorgnet - Claro. Então, meu caso de uso foi, eu tenho uma barra de ferramentas comum em todas as páginas do meu aplicativo, mas dentro da barra de ferramentas eu queria adicionar botões e ferramentas para funcionalidades adicionais com base na página em que eu estava. Para conseguir isso, criei um componente para conter esses botões/ferramentas e sua lógica e queria manter esses componentes dentro do módulo ao qual estão associados. Então, essencialmente, nada fora do meu módulo sabe nada sobre essas funções específicas da barra de ferramentas enquanto ainda é capaz de mostrá-las na barra de ferramentas comum. Abaixo está minha solução de trabalho que espero que ajude você:

Tudo o que estou fazendo com o serviço de barra de ferramentas é criar um observável, para que seu componente preguiçoso possa passar suas referências componentFactoryResolver e injector para o componente comum da barra de ferramentas que está ouvindo o receiveContext Evento.

barra de ferramentas.serviço.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 });
    }
}

Em seu componente carregado preguiçosamente, você desejará injetar componentFactoryResolver e injector e disparar um evento no serviço da barra de ferramentas.

componente de barra de ferramentas carregado preguiçosamente

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

Finalmente, no meu componente principal da barra de ferramentas, estou assinando o observável no serviço da barra de ferramentas para que, sempre que um evento for acionado, ele tentará injetar e criar o componente lento de que preciso. Também é importante destruir o componentRef que você criou, caso contrário você acabará com vazamentos de memória.

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

Seu componente criado dinamicamente será colocado onde quer que você coloque o #toolbarTarget correspondente

<div #toolbarTarget></div>

Como um aparte, estou usando esta linha this.component = data['toolbar']; no componente de barra de ferramentas comum para obter a referência do componente carregado com preguiça da rota. Se você quiser ver o código completo para isso, posso postar isso, mas está fora do escopo desta discussão.

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

Deixe-me saber se você tiver mais alguma dúvida!

@jmcclanahan Eu tenho uma pergunta (provavelmente uma estúpida) sobre o seu código, quando você menciona o "componente de barra de ferramentas carregada com preguiça", você coloca o código de chamada de serviço na seção ngOnInit, mas como você consegue inicializá-lo (o componente) sem precisar adicioná-lo ao código HTML e escondê-lo posteriormente no DOM? Como esse componente só será usado na barra superior, não o carrego em nenhum outro lugar antes.

@Dunos - Percebi que deixei completamente essa parte de fora do meu exemplo acima, desculpe por isso! Você desejará adicionar o componente dinâmico como um entryComponent no módulo carregado com preguiça ao qual está associado.

Então, no próprio componente da barra de ferramentas, você definirá um ViewContainerRef que fará referência ao local em seu modelo html onde você deseja que o componente dinâmico seja exibido. Então o método updateToolbar() cuidará de criar seu componente, colocando-o no local especificado e mostrando o componente na barra de ferramentas.

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

Seu componente criado dinamicamente será colocado onde quer que você coloque o #toolbarTarget correspondente

<div #toolbarTarget></div>

O método clearComponent() é chamado sempre que ngOnDestroy() é executado, que se encarrega de ocultar o componente quando você sai da página.

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

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

Eu também atualizei meu post original acima. Tenho recebido muitas perguntas sobre isso, então adicionarei um exemplo de trabalho usando esta barra de ferramentas dinâmica assim que puder e atualizarei vocês com um link.

@jmcclanahan que recebo (eu acho), mas ainda não entendo como a chamada para o serviço é feita a partir do componente dinâmico (a parte de chamada this.toolbarService.receiveContext ) já que o ngOnInit nunca é chamado (ou eu não t veja como fazê-lo). Eu adicionei ao entryComponents, mas preciso inicializá-lo um pouco para que o onInit funcione.

@Dunos - Talvez um exemplo seja melhor. Digamos que eu esteja navegando da página de índice do meu aplicativo para a Página A. Você terá uma rota correspondente para a Página A, que definirá o módulo/componente da Página A. A página A também terá um componente separado definido que desejo exibir na barra de ferramentas global. Então, dentro do componente principal da Página A você vai injetar ComponentFactoryResolver e Injector e então disparar um evento receiveContext . Este pedaço de código:

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

Desculpe, eu pude ver como isso pode ser confuso. Você não coloca esse código no componente que deseja exibir na barra de ferramentas, mas sim no componente associado à página em que está.

@jmcclanahan Ah perfeito, muito obrigado!!! Deu certo agora :)

@jmcclanahan Acabei de resolver o mesmo problema antes de encontrar este post. Uma coisa que notei é que se você quiser salvar alguns caracteres em suas declarações de função, você pode obter uma referência ao ComponentFactoryResolver usando o Injector que você passa. você pode apenas fazer:
injector.get(ComponentFactoryResolver);

E isso deve retornar a referência que você precisa. Mas no final dá o mesmo resultado que passar o ComponentFactoryResolver como um parâmetro na função também.

Isso resolve meu problema também.

Em suma, tudo o que você precisa saber é que quando você deseja criar dinamicamente um componente declarado em um módulo lento, você precisa realmente usar ComponentFactoryResolver de um componente declarado nesse módulo lento. O injetor raiz (que contém o ComponentFactoryResolver raiz) não saberá sobre nenhum entryComponents declarado em um lazyModule carregado.

Parece meio estranho, mas é assim que o Angular é projetado (o que faz sentido), mas isso na verdade cria complexidade extra ao trabalhar com o framework, imho.

Como a equipe Angular pode aproveitar isso: Sempre que uma rota é ativada, o injetor raiz deve anexar os entryComponents listados nesse lazyModule automaticamente e sempre que a rota for alterada, os entryComponents anexados anteriormente devem ser destruídos automaticamente, isso garantiria menos complexidade e evitaria excesso de inundação o injetor raiz com fábricas de componentes extras.

Vou enviar uma solicitação de recurso para isso.

Obrigado!

Muito obrigado @jmcclanahan

@jmcclanahan muito obrigado!! @renatoaraujoc você enviou esse recurso para a equipe Angular?

@rraya Enviei uma solicitação de recurso sobre esse "problema" (não é realmente um problema se você pensar sobre isso), consulte # 17168.

Apenas para adicionar a isso. Meu caso de uso é que criei um ModalManagerService no meu AppModule que possui um método aberto que todas as outras partes do aplicativo chamam para abrir um modal. Ele recebe um componente que é aberto como um modal. Eu também tenho um método closeAll que é usado quando os modais precisam ser fechados por alguma ação que não seja o usuário fechando-os, por exemplo, mudança de rota devido ao tempo limite da sessão.

@jmcclanahan , mas ainda não entendo o seu, muito obrigado se houver algumas sugestões. parece que ocorreu algum erro em 'this.componentFactoryResolver.resolveComponentFactory'

@Dunos você pode me dar uma mão? Eu sofri o mesmo problema por semanas, e é realmente uma dor de cabeça.

@werts ,

Tudo o que você precisa entender é que sempre que você navegar para uma rota de carregamento lento, a rota ativada gerará um injetor filho que contém os componentes de entrada para esse lazyModule. Se você tiver um modalService global definido no AppModule (que é o injetor raiz do aplicativo), o componentFactoryResolver não conhecerá nenhum novo entryComponents de qualquer lazyModule. Portanto, uma das maneiras de resolver esse problema é obter a instância de complementFactoryResolver de um componente lazyModule e de alguma forma dizer ao modalService "hey, use this injector/componentFactoryResolver para renderizar o entryComponent desejado".

Parece um pouco difícil de entender, mas não é.

@renatoaraujoc obrigado por sua explicação, no meu caso de uso, quero carregar o componente de um link, ou seja, clico no menu da barra lateral esquerda e recuperarei o módulo de um módulo carregado com preguiça e, em seguida, criarei um componente dinâmico, porque eu quero construir um aplicativo semelhante a uma guia, então, na área de conteúdo principal à direita, há algum conteúdo de componente, se eu clicar no gatilho da guia, o conteúdo será exibido. como aplicativo via angular2/4?

de qualquer forma, o componente lazyload nunca pode ser init, como posso dizer ao serviço seu componentFactoryResolver?

para meu aplicativo, existe um AppModule, e ele contém CoreModule, então, meu ContentTabModule (usado para carregar tabset) pertence ao CoreModule,,todos os componentes de criação manipulados pelo ContentTabModule,tem service,component,directive,pipe,quando clicar em um menu link, o ContentTabService irá criar um componente dinâmico a partir do menu params.eu vi algum trunk(eu uso webpack)carregado no network panel.and então eu quero criar o componente que demonstrou no módulo lazyload.

@renatoaraujoc como posso obter a instância de complementFactoryResolver de um componente do lazyModule?acho muito mais difícil, pois o componente do lazymodule nunca é init(ou estou errado)

@werts confira a conversa que tive com @jmcclanahan acima, falamos sobre algo semelhante com uma barra de ferramentas.

@Dunos não consigo entender o exemplo de @jmcclanahan , acho que sofro o mesmo. o componente lazyload nunca é iniciado.
this.toolbarService.receiveContext() chamado do módulo lazyload?

@werts você tem que carregar o componente principal do módulo lazy com as rotas, e esse componente é aquele que chama o serviço aux com seu Injector e ComponentFactoryResolver.

@Dunos obrigado, você pode me dar uma mão se for conveniente para você, algum outro caso de uso, eu quero carregar um módulo com um clique no link e, em seguida, criar um componente dinâmico.

@werts ... sim, é possível. Veja https://github.com/Toxicable/module-loading para o ponto de partida.

ok, tente agora, thx. @mlc-mlapis

@werts et al: Posso recomendar que você leve este tópico de suporte para outro canal? StackOverflow ou Gitter podem ser mais adequados. Do jeito que está, estou inscrito neste problema (como tenho certeza que muitos outros) e está gerando muito barulho no momento, o que está ficando um pouco fora do tópico.

No lado positivo: ótimo ver uma comunidade tão útil!

Não posso acreditar que esse problema também seja outro obstáculo que tenho que enfrentar com módulos carregados com preguiça. Eu tenho procurado desesperadamente uma solução para manter os filhos do meu módulo preguiçoso anônimos de seu pai (app.module) para que eu possa remover/exportar facilmente o módulo preguiçoso, se necessário. Para algo tão simples como um aplicativo <router-outlet name="app-popup"> em app.component.html (do qual eu desisti na esperança de que ComponentFactory possa substituir seu trabalho) apenas carregar componentes filhos preguiçosos tem sido muito assustador. Só para agora encontrar o problema dos componentes de entrada com os filhos de entrada dos módulos preguiçosos. Simplesmente não é possível manter meus módulos isolados corretamente se eu precisar que o módulo raiz registre componentes filhos preguiçosos. Só espero que a equipe do Angular perceba isso como um problema em breve e o resolva. Ainda estou esperando que eles respondam ao meu problema com uma saída de roteador em todo o aplicativo para módulos preguiçosos. https://github.com/angular/angular/issues/19520

No começo não havia nada,
'Vamos criar uma visão! ', diz a equipe jQuery,
'Vamos fazer uma lista! ', diz a equipe Angular.

@jmcclanahan temos ngComponentOutlet agora, torna as coisas muito mais simples.

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

@crysislinux ngComponentOutlet é ótimo, mas ainda não nos permite passar dados para o componente, então não é uma solução.

Alguma novidade sobre esse recurso? =/

Acabei de me deparar com esta questão. Tentei algumas das sugestões, sem sucesso. Eu consegui funcionar, no entanto. Na verdade, acabou que meu módulo carregado preguiçosamente estava faltando uma importação de outro módulo dependente - neste caso, o módulo modal NGX - mas o erro acusou meu componente de não ser resolvível.

Estou curioso: esse problema é resolvido pelo uso do ui-router? Ou talvez até mesmo a capacidade de várias tomadas do próprio roteador do angular? Eles fazem essa lógica nos bastidores? Este é um caso muito comum para mim em angular 1.x e estou tentando fazer a transição para angular 2+

No exemplo da barra de ferramentas, a ui-view (provavelmente uma visão nomeada ou saída de roteador no caso de roteador angular), seria configurada em um módulo de nível superior. Se um estado de módulo carregado com preguiça substituir a visualização de interface do usuário da barra de ferramentas por seu próprio conteúdo, ele teria que apenas "funcionar" ou o roteador de interface do usuário não funcionaria em um ambiente carregado com lentidão, onde as visualizações de nível superior fora do módulo atual precisam ser substituídas . Isso seria extremamente limitante em comparação com 1.x angular.

O exemplo acima parece estar resolvendo a mesma coisa que o roteador ui/angular foi projetado para resolver. Estou esquecendo de algo?

... provavelmente estamos perdidos qual é o problema. Como sabemos e usamos a cada dia não há problema em usar componentes de módulos carregados preguiçosamente... mesmo via roteador ou programaticamente pelo nosso próprio código.

Ao usar o exemplo @jmcclanahan acima, parece funcionar bem para o meu caso de uso.

Eu tive que adicionar this.componentRef.changeDetectorRef.markForCheck(); depois this.componentRef = this.toolbarTarget.createComponent(childComponent, 0, this.refInjector);.

Isso corrigiu um problema que tive com as propriedades HostBinding não sendo acionadas.

Imaginei que daria uma olhada em uma solução em potencial que funcionou para mim. Este é um fork do plunkr original.
http://plnkr.co/edit/x3fdwqrFDQr2og0p6gwr?p=preview

Para contornar isso, você precisa resolver a fábrica de componentes de um serviço (ResolverService) fornecido no módulo que possui o entryComponent (Page1Module). Isso é então passado para um segundo serviço (ChangerService) que usa a fábrica resolvida e adiciona o componente à exibição no MainComponent.

O que eu realmente não entendo é esse maldito roteador. Verifique este plunker (bifurcado do exemplo de @patkoeller )
http://plnkr.co/edit/keMuCdU9BwBDNCaMUAEU?p=preview @ src/page1.ts

O que eu basicamente quero fazer é injetar algum componente no aplicativo de nível superior e usar a diretiva [routerLink] com comandos relativos à definição do módulo carregado com preguiça como: [routerLink]="['./', 'sub', 'component'] para gerar /page-1/sub/component . Se eu passar um injetor para createComponent com alguns ActivatedRoute e funcionar para o meu caso, estraga todos os outros roteadores, mas se eu reatribuir router.routerState.snapshot ao state da resolva e passe o injetor com esta instância do roteador parece funcionar como pretendido... muito confuso!

Devo registrar um bug?

Estou enfrentando o mesmo problema com componentes de entrada para meu serviço singleton de diálogo. Os componentes modais relacionados ao recurso devem permanecer em seus respectivos módulos sem a necessidade da solução alternativa de @jmcclanahan .

Algum novo ETA sobre isso? Ou pelo menos uma resposta da equipe Angular? Não ter essa funcionalidade é realmente ruim, já que o encapsulamento do módulo acabou de ser jogado pela janela. Todos os diálogos para @angular/material precisam ser importados no módulo raiz.

@xban1x Acho que não temos nenhum progresso com isso. Temos que fazer o walkaround :(

@jmcclanahan é possível obter uma referência do componente injetado usando sua abordagem? Eu injetei com sucesso um componente, é uma árvore btw, então agora eu preciso me inscrever nos eventos de clique da árvore no componente, que injetou a árvore. Existe uma maneira de realizar isso?

Na edição #23819, perguntei sobre como fazer os componentes de entrada se comportarem de maneira semelhante aos serviços com providesIn: 'root' . Encerrei esse problema porque parece que ambos os problemas se destinam a resolver a mesma coisa. Espero que esse problema seja resolvido em breve, porque esse é um recurso importante :)

Atualizar

Pelo que vejo no código não minificado, quando providesIn está definido no serviço, ele registra a fábrica para que já possa ser utilizada (sem a necessidade de importar em um módulo), se entendi corretamente:

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

Suponho que exista uma maneira de aplicar uma lógica semelhante em um componente com alguma opção como @Component({ ... entryComponent: true }) para que quando o pedaço que o contém seja carregado, a fábrica seja definida no resolvedor (deve haver um módulo que inclua o componente em entryComponents com as dependências do componente, caso contrário, causaria um erro de tempo de compilação).

@lucasbasquerotto ... usamos a seguinte lógica para carregar um módulo e usar seus tipos de componentes (com nosso próprio mapeamento entre chaves de string <-> tipos de componentes armazenados diretamente nesse 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, []);
    });

Olá pessoal, por enquanto também estou usando a solução alternativa de ter uma classe singleton que mantém uma referência aos vários resolvedores de módulo carregados com preguiça.
Existe alguma maneira melhor de lidar com isso com a nova versão do Angular?
Obrigado

Oi a todos,

Apenas enfrentando o mesmo problema e uso a solução alternativa como bootstrap para minha solução.

Uma forma de ir mais longe e reduzir a necessidade de ter um serviço global, seria expor a variável privada loadConfig do evento RouteConfigLoadEnd. Esta variável privada do tipo LoadedRouterConfig contém o módulo carregado, seu injetor e seu componentFactoryResolver.

Devo abrir um novo problema para propor isso como uma solicitação de recurso?
--------- Após a edição

Não importa minha pergunta, encontrei esta solicitação de recurso https://github.com/angular/angular/issues/24069

Acho que tenho o mesmo problema, mas meu módulo extra não é lazy loaded . Eu tenho que mover meu entryModules de SharedFeatureModule (que é carregado apenas se for necessário) para CoreModule .
Agora funciona. Mas mudei o componente relacionado ao recurso para o núcleo, o que não está bem na minha opinião.

Este problema ainda não foi resolvido?

Solução de @jmcclanahan

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

Adiciona uma rota em branco extra ao final. Quando tento criar um breadcrumb dinâmico, isso está se tornando um problema.

Sem adicionar esse padrão de rota em branco ao componente, prefiro ter algo semelhante ao bootstrap em módulos filho preguiçosos.

Alguma atualização sobre isso?

Nossa solução personalizada envolve a injeção do construtor ComponentFactoryResolver e Injector em cada componente que poderia abrir nossa barra lateral global. O componente de chamada então passa essas referências (suas próprias) para um serviço junto com o Component desejado. O serviço usa essas instâncias para criar o componente. Complicado demais e verboso.

Basta colocar seus componentes de entrada em seu próprio submódulo e importá-lo para o aplicativo e em nenhum outro lugar.

@broweratcognitecdotcom Acho que o objetivo deste problema é entryComponents em módulos carregados com preguiça . Incluir todos os submódulos de cada componente que você deseja usar como componente de entrada em AppModule forçará todos esses componentes a serem carregados rapidamente, o que pode causar problemas de desempenho na inicialização .

Para não dizer que esses componentes devem ser agnósticos em relação aos componentes que os utilizam (os consumidores), de forma que seus módulos não saibam se estão sendo usados ​​como componentes de entrada ou componentes normais (declarados no html), apenas seu consumidor deve saber (então, colocá-los no AppModule dificultaria saber por que eles estão lá, você precisaria pesquisar em outros lugares para saber).

@lucasbasquerotto ... carregue esses módulos preguiçosos programaticamente ... você tem controle total então, e simplesmente funciona.

Eu tropecei neste tópico depois de encontrar um problema estranho em que não consegui resolver o componente do resolvedor raiz usando um mapa de chaves de string e componentes correspondentes. Eu posso literalmente ver o componente no mapa _factories, mas de alguma forma ele não compara corretamente e retorna indefinido.

@jmcclanahan @Dunos Você não acha que, em vez do módulo de recurso entregar sua referência de injetor (registrando seu contexto com o módulo principal do aplicativo), será melhor se ele expor um serviço, digamos FactoryResolver, que realmente retornará a fábrica resolvida corretamente para seu componentes, pois terá referência correta ao injetor do seu próprio módulo. Dessa forma, será como expor uma API pública para seus componentes.

Estou tentando fazer algo semelhante, mas meu módulo não é carregado com preguiça e estou esbarrando no problema que descrevi acima. Em teoria, deve funcionar, a menos que eu esteja perdendo algo óbvio aqui?

@h-arora notei o mesmo problema, vendo o comp. em _factories, mas o problema era que o módulo tinha um erro de compilação na verdade, que não apareceu

Copiando de # 17168 (fechado como uma duplicata deste)

Comportamento atual
Se você tiver um serviço singleton que usa um ComponentFactoryResolver fornecido pelo RootInjector do aplicativo para criar componentes dinamicamente e deseja criar um entryComponent declarado em um módulo lento, isso falhará porque o injetor raiz não sabe disso.

Comportamento esperado
Este é realmente o comportamento esperado, considerando que o injetor raiz não saberá sobre entryComponents conhecidos pelo injetor filho.

Qual é a motivação/caso de uso para mudar o comportamento?
A motivação é aproveitar a complexidade para criar componentes dinâmicos, embora o design modular do Angular esteja correto.

Versão angular: 2.4.9

Solução proposta: Embora isso vá contra o design modular adotado pelo próprio Angular, acho que a complexidade fornecida para criar componentes dinâmicos poderia ser suavizada simplesmente anexando os entryComponents do LazyModule aos entryComponents do RootInjector e evitando inundar o RootInjector, sempre que você navegar (que destrói o LazyModule), os entryComponents injetados anteriormente seriam apagados do array.

Simplesmente falando:

A página é carregada -> RootInjector é criado.
Emite uma navegação que ativa um LazyModule -> ChildInjector é criado.
RootInjector.entryComponents.append(childInjector.entryComponents)
Emite uma navegação para outro lugar -> LazyModule não é mais necessário, portanto, destruído.
RootInjector.entryComponents.destroyLastAppendedEntryComponents()
Espero poder sugerir algo bom.

Passei alguns dias separando o projeto da minha empresa e implementando o carregamento lento. Foi só no final que percebi que nossos modais não estavam funcionando por serem entradas em seus respectivos módulos. Retornamos e estamos aguardando uma solução...

Isso é algo que está agendado ou devemos explorar uma solução alternativa. Como as pessoas estão fazendo acima.

@ravtakhar , você provavelmente deseja ver a solução alternativa, pois isso está no Backlog e não tem uma prioridade atribuída neste momento.

Passei alguns dias separando o projeto da minha empresa e implementando o carregamento lento. Foi só no final que percebi que nossos modais não estavam funcionando por serem entradas em seus respectivos módulos. Retornamos e estamos aguardando uma solução...

Isso é algo que está agendado ou devemos explorar uma solução alternativa. Como as pessoas estão fazendo acima.

Mesmo problema aqui

@tmirun ... se você carregar esses módulos preguiçosos programaticamente ... você tem o controle total, ... significa que você pode obter uma referência de módulo finalmente e chamar moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponentType)

Teve o mesmo problema que muitos acima e resolveu criando um módulo autônomo declarando e exportando todos os entryComponents, incluindo aquele no appModule e exportando-o também, para que cada módulo lazyLoaded carregue esses componentes.

A única outra solução viável que encontrei também foi gerar diálogos fornecendo um ComponentFactoryResolver personalizado de acordo com o módulo que hospeda o entryComponent .

CUIDADO : lembre-se que os módulos singleton fornecidos no root só verão os componentes de entrada do módulo principal , por isso decidi apresentar a primeira solução, caso contrário você pode pensar em fazer um gerador de diálogo para cada submódulo de sua aplicação e, em esse submódulo, eventualmente incluirá módulos compartilhados que possuem outros entryComponents , fornecendo ao serviço gerador dos modais os ComponentFactoryResolver dos referidos módulos.

... a solução geral suportada deve estar disponível (algumas novas APIs + suporte em Ivy) ... devido à apresentação de Jason http://www.youtube.com/watch?v=2wMQTxtpvoY&t=131m24s ... em Angular 7.2 . .. mas não tenho certeza se a versão 7.2 foi mencionada corretamente.

Eu tenho o mesmo problema com o CDK Overlay , tenho um plugin de galeria que é construído em cima do CDK overlay, mas por causa desse problema eu tenho que importá-lo no módulo raiz.

BTW, existe alguma solução alternativa para o meu caso?

@MurhafSousli ... lol, devo dizer novamente que é por design. A coisa foi explicada centenas de vezes ... há um injetor de raiz e qualquer módulo carregado com preguiça tem um nível de profundidade, então você não pode supor que é capaz de tocar seus componentes no nível de raiz. Mas como foi dito muitas vezes você pode carregar esse módulo programaticamente... assim você obterá essa referência de módulo... e poderá acessar seus componentes então.

Me adiciona na lista também 😄. Eu tenho um componente modal onde carrego meus componentes dinamicamente e não quero carregar todos eles no aplicativo que seria um design ruim, pois eles não fazem parte do componente do aplicativo. Mas isso é o que eu tenho que fazer eu acho que depois de ler tudo isso.

@msarica apontou para mim hoje que, se você criar um serviço carregado lentamente e estender seu serviço global que abre pop-ups, use o serviço carregado lentamente para abrir o pop-up, ele funcionará conforme o esperado:

Global PopupService

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

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

Serviço específico apenas carregado lentamente em MyFeatureModule

@Injectable()
export class MyFeaturePopupService extends PopupService {}

Uso em um componente que faz parte do MyFeatureModule carregado lentamente

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

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

Exemplo de definição de módulo carregado lentamente

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

Testado em Angular 7.

Nota: Nosso código aproveita o Overlay do Angular CDK, mas o conceito é o mesmo (de acordo com outras partes deste tópico, é o Injector que preocupa).

... a solução geral com suporte deve estar disponível (algumas novas APIs + suporte em Ivy) ... em Angular 7.2

... @mlc-mlapis você poderia expandir qual seria a solução geral com suporte?

@cedvdb ... acabei de mencionar o momento importante nessa apresentação que realmente parece uma solução ... significa a capacidade suportada de carregar programaticamente um módulo carregado com preguiça e usar qualquer parte dele, como um componente, uma diretiva, ...

Essa forma está disponível até hoje ... mas com o Angular CLI você sempre precisa definir as rotas necessárias ... mesmo que você nunca as use através do roteador.

Uau. Isso foi muito inesperado.

Eu implementei meu próprio sistema de diálogo baseado em portais e sobreposições @angular/cdk: https://stackblitz.com/edit/cdk-dialog-example-p1. Tem esse problema também.

A solução alternativa mencionada pelo @CWSpear funciona: tive que estender o serviço no meu módulo de recursos e funcionou para mim. Que estranho!

Existe uma solução mais elegante para isso?

Existe uma solução mais elegante para isso?

Algo que tenho experimentado é fornecer novamente um wrapper do iniciador no nível do módulo preguiçoso. Faça com que esse wrapper pegue um injetor no construtor e passe-o para o lançador real (que é injetado da raiz). É uma correção de uma linha. Não tenho certeza de quão estável é, no entanto,

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

Meu problema foi causado porque usei @Injectable({providedIn: 'root'}) . Mudar para @Injectable({providedIn: MyModule}) resolveu. 😄

Isso faz sentido, porque o serviço precisa de acesso aos componentes de entrada dos módulos de carregamento lento. Quando fornecido na raiz, não pode acessá-los porque o módulo está temporariamente desconectado do injetor raiz.

Muito obrigado a @jmcclanahan e @dirkluijk , fiquei horas coçando a cabeça e sua descrição ajudou a encontrar uma solução para o meu caso.

Eu só quero saber qual é a maneira certa de fazer isso.
É necessário @angular/cdk ? Por quê ?

Não acho que qualquer solução alternativa seja viável se exigir que o código com o qual você está se integrando saiba disso, porque há muitos códigos de terceiros que usam apenas ComponentFactoryResolver sem saber sobre seu registro alternativo especial.

Com isso em mente, aqui está minha "solução": CoalescingComponentFactoryResolver . Este é um serviço que deve ser fornecido pelo módulo app e inicializado em seu construtor, assim:

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

Em seguida, os módulos de carregamento lento devem injetá-lo e registrar suas próprias instâncias ComponentFactoryResolver com ele. Igual a:

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

Quando isso for feito, os componentes de entrada no módulo de carregamento lento devem estar disponíveis em serviços de carregamento não lento.

Como funciona: Quando inicializado, ele injeta o ComponentFactoryResolver do aplicativo root e o macaco corrige o método resolveComponentFactory para chamar sua própria implementação. Essa implementação primeiro tenta resolver a fábrica de componentes em todos os resolvedores de módulo lento registrados e, em seguida, retorna ao resolvedor de aplicativo raiz (há um pouco de lógica extra para evitar chamadas cíclicas, mas essa é a essência).

Então, sim, um hack bem nojento. Mas funciona, neste momento, em Angular 7. Talvez sirva para alguém.

Com base na solução do @CWSpear , consegui fazer isso funcionar simplesmente adicionando meu PopupService existente ao array de provedores do componente. Portanto, não precisei criar um PopupService "específico ao recurso" separado.

Assim assim:

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

Como alternativa, você também pode adicionar o PopupService à matriz de provedores do NgModule do módulo de carregamento lento. Por exemplo:

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

@jonrimmer melhor solução

@jonrimmer Isso é minimamente intrusivo e funciona perfeitamente para o nosso caso de uso. Um enorme obrigado meu e da minha equipa :)

Estou curioso para saber como funciona sem problemas no caso de roteador lento, mas gera erro quando removi a rota lenta e carrego os 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 Isso é para o modo JIT. Você precisa carregar import("../bar/bar.module.ngfactory") para o modo AOT (onde compileModuleAndAllComponentsSync não está disponível).

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

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

Pode usar AOT JIT. sucesso para mim.

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

``` texto datilografado
import { Compiler, Inject, Injectable, NgModuleFactory, NgModuleRef, Type, ComponentFactory } de '@angular/core';
importe { LoadChildrenCallback } de '@angular/router';

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

@Injectable({
fornecidoIn: 'root'
})
classe de exportação LazyComponentLoader {
módulos privadosToLoad: Map;
módulos privadosLoading = new Map>();

construtor(
private moduleRef: NgModuleRef,
@Inject(ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN) elementModulePaths: Mapa,
compilador privado: Compilador
) {
this.modulesToLoad = new Map(elementModulePaths);
}

load(path: string, seletor?: string): Promise{
if (this.modulesLoading.has(path)) {
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 ... então correto. Está usando o compilador apenas no modo JIT.

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

@mlc-mlapis AOT será NgModuleFactory. Você pode testar https://github.com/Jimmysh/ng-lazy-component-load

Ei pessoal, com a ajuda dos comentários deste tópico eu criei um pacote que se destina a ser uma solução alternativa para tentar obter componentes de entrada carregados fora do módulo por meio de roteamento. Por favor, dê uma olhada e espero que ajude.

Link do repositório: https://github.com/Jonathan002/route-master-example
URL de demonstração: https://jonathan002.github.io/route-master-example/

FWIW, de maneira semelhante a @CWSpear e @dirkluijk , removi o providedIn: 'root' do meu serviço (definido em um módulo carregado com preguiça e responsável por abrir um entryComponent ) e especifiquei o serviço no providers da minha definição de módulo preguiçoso. Acredito que a maior/única desvantagem disso é que o serviço não pode mais ser abalado em árvore.

O uso providedIn: MyLazyModule levou a avisos de dependências circulares.

@jonrimmer não poderia deixar o tópico sem agradecer!! você salvou meu dia.
Para todos que estão lendo meu comentário, pode ajudá-lo a não perder seu tempo se você olhar para esta resposta maravilhosa .

Confirmado, isso ainda não foi resolvido no Angular 8.2.0. Desde que o OP foi criado há 2,5 anos, o cenário do Angular mudou muito, e o Angular Material 2 se tornou Angular Components.

Conforme indicado pelos outros comentaristas, o Angular Material usa DOM para que os desenvolvedores de aplicativos não precisem usar DOM ao carregar um componente de aplicativo no MatDialog, mesmo que o componente do aplicativo seja declarado no entryComponents do submódulo carregado lentamente.

No entanto, fazer as mesmas coisas do DOM em um aplicativo de negócios parece estranho, contra as diretrizes de programação de aplicativos do Angular.

Será bom que a solicitação de recurso do OP possa ser implementada para que ComponentFactoryResolver possa "ver" um componente de entrada declarado em um submódulo carregado preguiçosamente, de uma forma ou de outra. Declarar um componente de entrada de um módulo lento em app.module está funcionando, mas é feio.

Além das soluções de trabalho sujas mencionadas acima: 1. DOM, 2. Declarando o componente em app.module, eu forneceria outra solução, menos suja, abaixo.

a 3ª solução, menos suja.

Eu tenho um serviço compartilhado usado por vários módulos carregados preguiçosamente.

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 serviço pode ver componentes de entrada de módulos não carregados lentamente. Tenho alguns componentes de entrada implementados em alguns módulos carregados preguiçosamente. Então, eu derivei a classe deste serviço a ser fornecida em cada módulo preguiçoso:

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

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

Como LazyComponentDialogService é fornecido no mesmo módulo lazy com o componente lazy entry, ele pode ver o componente.

Dependendo da arquitetura do seu aplicativo, você pode não precisar da classe derivada, mas apenas fornecer DataComponentDialogService ou algo semelhante em cada módulo.

Parece que o problema não existe mais no Angular 9 devido à implementação do ivy (testado com 9.0.0-next.7). Você pode criar dinamicamente um componente de um módulo carregado com preguiça de fora do módulo lento. Parece que a declaração do componente de entrada não é mais necessária para componentes dinâmicos no Ivy. Ivy não precisa de uma fábrica de componentes separada que costumava ser gerada pelo compilador. As informações da fábrica de componentes são incorporadas na definição do componente com Ivy(?). Depois de ter um tipo de componente, você poderá obter sua fábrica de componentes e criar uma instância dela. Essa é apenas a minha impressão. Tome minha palavra com um grão de sal

Tudo o que você precisa fazer é criar um módulo de widgets separado que declare todos os seus componentes dinâmicos e também os inclua como entryComponents. Em seguida, importe este módulo do AppModule. Isso garantirá que seus componentes de entrada sejam registrados com o injetor raiz.

Esta solução é limpa e não quebra o encapsulamento Lazy.

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

}

Módulo de aplicativo:

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

@pixelbits-mk Isso faria com que todos os componentes dinâmicos (como modal e similares) fossem carregados antecipadamente, mas o ponto aqui é torná-los capazes de serem carregados com preguiça para não diminuir a carga inicial do aplicativo (especialmente à medida que o projeto cresce ), assim como os componentes que os chamam.

Digamos que um ComponentA carregado com preguiça chame dinamicamente um ComponentB carregado com preguiça dinâmica. Se ComponentA sabe que vai chamar ComponentB você pode simplesmente importar ComponentBModule em ComponentAModule . Este não é o caso ideal, mas eu posso viver com isso.

O principal problema surge (na minha opinião) quando ComponentA não sabe que ComponentB será carregado . Por exemplo, se ComponentB for um modal e ComponentA chamar um método showModal() em um serviço e o serviço carregar ComponentB dinamicamente nos bastidores, ComponentA não saberia disso.

O que faço agora é incluir a referência ComponentB como parâmetro no método, como showModal(ComponentB) , de forma que ComponentA agora saiba que o ComponentB será carregado, mas isso é ruim na minha opinião (cada chamada ao método deve passar a referência do componente).

Se os componentes de entrada estivessem disponíveis sem ter que importar seus módulos explicitamente (o que eu acho que é esse problema), isso o resolveria (talvez o Angular pudesse ter uma maneira de detectar que ComponentA chama um serviço que importa ComponentB e fazer ComponentAModule import implicitamente ComponentBModule , e embora possa ter o efeito indesejável de também importar módulos de componentes cujas referências são importadas, mas o motivo da importação não é criá-los dinamicamente , eu veria isso mais como um caso extremo e ficaria bem com isso).

Eu não testei (ainda) a sugestão do @jonrimmer , mas isso parece promissor.

Seria incrível se ComponentA carregasse automaticamente o módulo lento com ComponentB somente se ComponentA fosse exibido:
ModuleA tem o componente ComponentA1 , ComponentA2 .
ComponentA1 referência em html ComponentB (no módulo ModuleB ), mas ComponentA2 não.
ModuleB seria carregamento lento automaticamente somente se ComponentA1 for exibido, mas não se ComponentA2 for exibido.

Isso se encaixaria perfeitamente no cenário de painéis na página inicial. Dahsboards vem de muitos módulos diferentes, mas se o dashboard não estiver incluído (digamos, através das configurações do usuário), não há necessidade de baixar esse módulo.

@jonrimmer Ótima ideia para envolver a função resolveComponentFactory do ComponentFactoryResolver principal e pesquisar os módulos de carregamento lento registrados antes.

Ele funciona com componentes de entrada que injetam serviços fornecidos na raiz. Mas não consigo injetar um serviço que é fornecido apenas no módulo lazy load. Neste caso, recebi um StaticInjectorError. Por favor, pode verificar?

Eu queria confirmar que a hera parece cuidar desse problema. Acabei de tropeçar nesse problema hoje quando reescrevi meus módulos para serem carregados com preguiça que continham componentes de entrada e serviços compartilhados para instanciá-los apenas para encontrar um novo erro informando que a fábrica de componentes não pôde ser encontrada. Fiquei realmente chocado ao ler que isso era um problema em primeiro lugar.

De qualquer forma, acabei de habilitar o ivy no meu projeto angular 8.2 existente e tudo parece estar funcionando conforme o esperado.

Minha solução para usar o Overlay em módulos carregados com preguiça:
Você tem que passar o resolvedor de fábrica para o construtor de ComponentPortal assim

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

mas você também precisa fornecer OverlayService no módulo carregado com preguiça. É necessário que o OverlayService injete ComponentFactoryResolver do módulo no qual o OverlayService é fornecido

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

@mixrich Essa abordagem funcionará [no meu aplicativo, estou fazendo assim e funciona bem até precisar fechar todos os diálogos modais em algum evento como logout], mas lembre-se de que isso instanciará o OverlayService toda vez que outro módulo importa este módulo, ou seja, o aplicativo não terá uma única instância como os serviços costumavam ser.

Como não haverá sinalização do serviço de sobreposição, NÃO será fácil saber quantos diálogos modais estão abertos.

Claro, são requisitos do aplicativo, mas apenas para ser cauteloso com essa abordagem.

@mixrich Essa abordagem funcionará [no meu aplicativo, estou fazendo assim e funciona bem até precisar fechar todos os diálogos modais em algum evento como logout], mas lembre-se de que isso instanciará o OverlayService toda vez que outro módulo importa este módulo, ou seja, o aplicativo não terá uma única instância como os serviços costumavam ser.

Como não haverá sinalização do serviço de sobreposição, NÃO será fácil saber quantos diálogos modais estão abertos.

Claro, são requisitos do aplicativo, mas apenas para ser cauteloso com essa abordagem.

Por exemplo, no meu caso eu tenho ModalDialogModule com OverlayService e quando eu chamo OverlayService.open(SomeComponent) ele também cria para mim o modelo de janela modal, insere SomeComponent dentro e retorna alguma estrutura de dados com observáveis ​​úteis (para eventos de fechamento e sucesso), instância de componente e método close manual.
Então, quando eu preciso usar modais no meu LazyModule, eu só preciso importar o ModalDialogModule para ele para obter a capacidade de usar OverlayService . Achei essa abordagem conveniente, pois você sempre sabe que para usar modal você tem que importar o ModalDialogModule , como você sempre sabe que para usar formulários reativos você tem que importar ReactiveFormModule

Eu tenho isso funcionando em Angular 8.3.6. Eu tenho um módulo de biblioteca com componentes de entrada (diálogos de mat) que não serão carregados se eu adicioná-los aos componentes de entrada dos módulos de biblioteca. Ele diz que não é possível encontrar a fábrica de componentes para MyCustomDialog no log do console quando tentei abri-lo.

A solução é criar um método estático na classe NgModule do módulo de biblioteca que retorne uma matriz de todos os componentes de entrada do módulo. Em seguida, chame esse método dos módulos do aplicativo 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 {
}

Eu sei que isso ainda não corrige os componentes de entrada na biblioteca não funcionando, mas pelo menos com o método estático o módulo do aplicativo não precisa saber sobre os componentes de entrada na biblioteca personalizada. Ele apenas chama esse método e obtém o que estiver listado na biblioteca. Não há responsabilidade no aplicativo para saber quais componentes da biblioteca são componentes de entrada.

@asmith2306 por que você não usa o operador de spread? parece feio

@asmith2306 por que você não usa o operador de spread? parece feio

Porque não funciona com isso. Há um bug sobre isso em algum lugar.

parece que não funciona com o modo --prod

Mesmo que tenha funcionado, não resolve o problema REAL. o problema é que se eu adicionar um componente aos entryComponents de um NgModule e esse módulo for importado para um módulo carregado preguiçosamente no meu aplicativo, os entryComponents deverão ser registrados automaticamente com os componentes de entrada de aplicativos. Não deve haver nenhum trabalho adicional necessário. Eu diria ainda que qualquer componente no entryComponents de qualquer NgModule deve terminar automaticamente no entryComponents do módulo do aplicativo. Este não é o caso atualmente. Se fosse, não estaríamos aqui.

@broweratcognitecdotcom E os conflitos e prioridades de diferentes níveis de injeção?

O pedido @mlc-mlapis deve ser por ordem de chegada, na minha opinião.

@mlc-mlapis Você quer dizer se componentes diferentes têm o mesmo nome? Primeira correspondência ou um erro se houver várias correspondências. Eu poderia viver com um requisito de que os nomes dos componentes de entrada fossem exclusivos.

@broweratcognitecdotcom Olá, sim. Porque todas as variantes devem ser cobertas se estivermos falando de princípios gerais.

@mlc-mlapis Acho que a limitação em angular que muitos gostariam de superar é que os componentes de entrada só podem ser instanciados e colocados no dom pelo app. O aplicativo não precisa saber sobre uma caixa de diálogo que um módulo que importo para um módulo carregado com preguiça usa. O conceito atual de componentes de entrada quebra o padrão modular do angular.

AFAIK com Ivy entryComponents não será mais necessário, certo? Como os componentes terão localidade, apenas importá-los dinamicamente sempre funcionará?

@Airblader Você está certo, nós sabemos. Foi apenas uma pequena reminiscência de volta à história. 😄

Para aqueles que ainda estão lutando com esses problemas, aqui está a solução que realmente funciona: https://github.com/angular/angular/issues/14324#issuecomment -481898762

Eu publico um pacote para essas questões. alguma cópia de código de jonrimmer.
Parece que o problema não existe no Angular 9. Se você precisar, corrija isso agora. você pode usar @aiao/lazy-component
código de exemplo aqui
https://github.com/aiao-io/aiao/tree/master/integration/lazy-component

Acredito que isso seja corrigido no Angular v9 executando o ivy. Se alguém ainda enfrentar problemas semelhantes, abra um novo problema com um cenário de reprodução mínima. Obrigado!

Este problema foi bloqueado automaticamente devido à inatividade.
Registre um novo problema se encontrar um problema semelhante ou relacionado.

Leia mais sobre nossa política de bloqueio automático de conversas .

_Esta ação foi realizada automaticamente por um bot._

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

alxhub picture alxhub  ·  3Comentários

gugamm picture gugamm  ·  3Comentários

mohsen1 picture mohsen1  ·  3Comentários

wfalkwallace picture wfalkwallace  ·  3Comentários

deepakgd picture deepakgd  ·  3Comentários