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
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).
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._
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.