Carregamento lento de componentes em Angular? Talvez estejamos falando de módulos de carregamento lento usando o roteador Angular? Não, estamos falando sobre os componentes. A versão atual do Angular suporta apenas o carregamento lento do módulo. Mas Ivy oferece ao desenvolvedor novas oportunidades de trabalhar com componentes.

A carga lenta que usamos até agora: rotas
O carregamento lento é um ótimo mecanismo. No Angular, você pode usar esse mecanismo sem ter que fazer quase nenhum esforço declarando rotas que suportam carregamento lento. Aqui está um exemplo da
documentação Angular
que demonstra isso:
const routes: Routes = [ { path: 'customer-list', loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule) } ];
Graças ao código acima, será criado um fragmento separado para
customers.module
, que será carregado quando o usuário navegar pela rota da
customer-list
.
Essa é uma maneira muito agradável de reduzir o tamanho do pacote principal do projeto e acelerar o carregamento inicial do aplicativo.
Mas, apesar disso, não seria bom poder controlar com mais precisão o carregamento lento? Por exemplo, e se pudéssemos organizar o carregamento lento de componentes individuais?
Até agora, isso não foi possível. Mas tudo isso mudou com o advento de Ivy.
Ivy e o conceito de "localidade"
Módulos são o conceito básico e o componente básico de qualquer aplicativo Angular. Os módulos declaram componentes, diretrizes, tubulações, serviços.
Um aplicativo Angular moderno não pode existir sem módulos. Uma razão para isso é o fato de o ViewEngine adicionar todos os metadados necessários aos módulos.
Ivy, por outro lado, adota uma abordagem diferente. No Ivy, um componente pode existir sem um módulo. Isso é possível graças ao conceito de localidade. Sua essência é que todos os metadados são locais para o componente.
Vamos explicar isso analisando o pacote configurável es2015 gerado usando o Ivy.
Pacote configurável Es2015 gerado usando IvyNa seção
Component code
do
Component code
, você pode ver que o sistema Ivy salvou o código do componente. Não há nada de especial aqui. Mas, em seguida, Ivy adiciona alguns metadados ao código.
O primeiro pedaço de metadados é mostrado na figura como uma
Component factory
. A fábrica sabe como instanciar um componente. Na seção
Component metadata
do
Component metadata
, o Ivy coloca atributos adicionais, como
type
e
selectors
, ou seja, tudo o que o componente precisa durante a execução do programa.
Vale ressaltar que Ivy adiciona a função de
template
aqui. É mostrado na
Compiled version of your template
seção
Compiled version of your template
. Vamos nos debruçar sobre esse fato interessante com mais detalhes.
A função de
template
é uma versão compilada do nosso código HTML. Ela segue as instruções da Ivy para criar um DOM. Isso é diferente de como o ViewEngine funciona.
O sistema ViewEngine pega o código e o ignora. Angular executará o código se o usarmos.
E com a abordagem usada por Ivy, o componente que chama os comandos Angular é responsável por tudo. Essa alteração permite que os componentes existam independentemente, isso leva ao fato de que o algoritmo de agitação de árvore pode ser aplicado ao código base Angular.
Um exemplo real de carregamento lento de componentes
Agora que sabemos que o carregamento lento de componentes é possível, considere-o com um exemplo real. Nomeadamente, criaremos um aplicativo de questionário que fará perguntas ao usuário com opções de resposta.
O aplicativo exibe uma imagem da cidade e opções a partir das quais você precisa escolher o nome dessa cidade. Assim que o usuário selecionar uma opção, clicando no botão correspondente, esse botão será alterado imediatamente, indicando se a resposta estava certa ou errada. Se o fundo do botão ficar verde, a resposta estava correta. Se o fundo ficar vermelho, significa que a resposta estava incorreta.
Depois que a resposta à pergunta atual é recebida, o programa exibe a seguinte pergunta. Aqui está como fica.
Quiz DemoAs perguntas que o programa faz ao usuário são representadas pelo componente
QuizCardComponent
.
Conceito de carregamento de componentes preguiçosos
Vamos primeiro ilustrar a idéia geral de carregar preguiçosamente o componente
QuizCardComponent
.
O processo do componente QuizCardComponentDepois que o usuário inicia o teste, clicando no botão
Start quiz
, iniciamos o carregamento lento do componente. Depois que o componente estiver à nossa disposição, colocamos em um recipiente.
Reagimos aos eventos
questionAnsvered
de um componente “preguiçoso” da mesma maneira que responderíamos aos eventos de um componente regular. Ou seja, após a ocorrência do evento, exibimos na tela o próximo cartão com a pergunta.
Análise de código
Para entender o que acontece durante o carregamento lento de um componente, começamos com uma versão simplificada do
QuizCardComponent
, que exibe as propriedades da pergunta.
Em seguida, expandiremos esse componente usando componentes de material angular. E no final, ajustaremos a reação aos eventos gerados pelo componente.
Vamos organizar o carregamento lento de uma versão simplificada do componente
QuizCardComponent
, que possui o seguinte modelo:
<h1>Here's the question</h1> <ul> <li><b>Image: </b> {{ question.image }}</li> <li><b>Possible selections: </b> {{ question.possibleSelections.toString() }}</li> <li><b>Correct answer: </b> {{ question.correctAnswer }}</li> </ul>
O primeiro passo é criar um elemento de contêiner. Para fazer isso, podemos usar um elemento real, como
<div>
, ou - recorrer ao
ng-container
, o que nos permite fazer sem um nível adicional de código HTML. É assim que a declaração do elemento container se parece com a qual colocamos o componente "preguiçoso":
<mat-toolbar color="primary"> <span>City quiz</span> </mat-toolbar> <button *ngIf="!quizStarted" mat-raised-button color="primary" class="start-quiz-button" (click)="startQuiz()">Start quiz</button> <ng-container #quizContainer class="quiz-card-container"></ng-container>
No componente, você precisa acessar o contêiner. Para isso, usamos a anotação
@ViewChild
e
@ViewChild
que queremos ler o
ViewContainerRef
.
Observe que no Angular 9, a propriedade
static
na anotação
@ViewChild
é definida como
false
por padrão:
@ViewChild('quizContainer', {read: ViewContainerRef}) quizContainer: ViewContainerRef;
Agora temos um contêiner no qual queremos adicionar o componente "preguiçoso". Em seguida, precisamos de
ComponentFactoryResolver
e
Injector
. Ambos podem ser adquiridos recorrendo à metodologia de injeção de dependência.
A entidade
ComponentFactoryResolver
é um registro simples que estabelece o relacionamento entre componentes e as classes
ComponentFactory
geradas automaticamente que podem ser usadas para instanciar componentes:
constructor(private quizservice: QuizService, private cfr: ComponentFactoryResolver, private injector: Injector) { }
Agora, temos tudo o que é necessário para atingir a meta.
startQuiz
trabalhar no conteúdo do método
startQuiz
e organizar o carregamento lento do componente:
const {QuizCardComponent} = await import('./quiz-card/quiz-card.component'); const quizCardFactory = this.cfr.resolveComponentFactory(QuizCardComponent); const {instance} = this.quizContainer.createComponent(quizCardFactory, null, this.injector); instance.question = this.quizservice.getNextQuestion();
Podemos usar o comando
import
ECMAScript para organizar o carregamento lento do
QuizCardComponent
. Uma expressão de importação retorna uma promessa. Você pode trabalhar com ele usando a construção
async/await
ou usando o manipulador
.then
. Depois de resolver a promessa, usamos a desestruturação para criar uma instância do componente.
Atualmente, você deve usar o
ComponentFactory
para fornecer compatibilidade com versões anteriores. No futuro, a linha correspondente não será necessária, pois poderemos trabalhar diretamente com o componente.
A fábrica da
ComponentFactory
nos fornece
componentRef
. Passamos
componentRef
e
Injector
para o método
createComponent
do contêiner.
O método
createComponent
retorna um
ComponentRef
que a instância do componente está contida. Usamos
instance
para passar as
@Input
do componente.
No futuro, tudo isso provavelmente poderá ser feito usando o método Angular
renderComponent
. Este método ainda é experimental. No entanto, é muito provável que ele se transforme em um método Angular regular.
Aqui estão materiais úteis sobre esse tópico.
Isso é tudo o que é necessário para organizar o carregamento lento de um componente.
Carregamento lento de componentesDepois que o botão
Start quiz
é pressionado, o carregamento lento do componente começa. Se você abrir a guia
Network
das ferramentas do desenvolvedor, poderá ver o processo de carregamento lento do fragmento de código representado pelo arquivo
quiz-card-quiz-card-component.js
. Após carregar e processar o componente é exibido, o usuário vê um cartão de perguntas.
Extensão de componente
No momento, estamos carregando o componente
QuizCardComponent
. Isso é muito bom Mas nossa aplicação ainda não é particularmente funcional.
Vamos corrigir isso adicionando recursos e componentes adicionais de material angular a ele:
<mat-card class="quiz-card"> <mat-card-header> <div mat-card-avatar class="quiz-header-image"></div> <mat-card-title>Which city is this?</mat-card-title> <mat-card-subtitle>Click on the correct answer below</mat-card-subtitle> </mat-card-header> <img class="image" mat-card-image [src]="'assets/' + question.image" alt="Photo of a Shiba Inu"> <mat-card-actions class="answer-section"> <button [disabled]="answeredCorrectly !== undefined" *ngFor="let selection of question.possibleSelections" mat-stroked-button color="primary" [ngClass]="{ 'correct': answeredCorrectly && selection === question.correctAnswer, 'wrong': answeredCorrectly === false && selection === question.correctAnswer }" (click)="answer(selection)"> {{selection}} </button> </mat-card-actions> </mat-card>
Nós incluímos alguns componentes de material bonitos no componente. E os módulos correspondentes?
É claro que eles podem ser adicionados ao
AppModule
. Mas isso significa que esses módulos serão carregados no modo "ganancioso". E isso não é uma boa ideia. Além disso, a montagem do projeto falhará, com a seguinte mensagem:
ERROR in src/app/quiz-card/quiz-card.component.html:9:1 - error TS-998001: 'mat-card' is not a known element: 1. If 'mat-card' is an Angular component, then verify that it is part of this module. 2. If 'mat-card' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
O que fazer Como você provavelmente já entendeu, esse problema é completamente solucionável. Isso pode ser feito usando módulos.
Mas desta vez vamos usá-los um pouco diferente do que antes. Adicionaremos um pequeno módulo ao mesmo arquivo que
QuizCardComponent
(no arquivo
quizcard.component.ts
):
@NgModule({ declarations: [QuizCardComponent], imports: [CommonModule, MatCardModule, MatButtonModule] }) class QuizCardModule { }
Observe que não há declaração de
export
.
Esta especificação do módulo pertence exclusivamente ao nosso componente, carregado no modo lento. Como resultado, o único componente declarado no módulo será
QuizCardComponent
. A seção de importação importa apenas os módulos que nosso componente precisa.
Não exportamos o novo módulo para que os módulos carregados no modo ganancioso não possam importá-lo.
Reinicie o aplicativo e veja como ele se comporta quando você clica no botão
Start quiz
.
Análise de Aplicação ModificadaÓtimo! O componente
QuizCardComponent
carregado no modo lento e adicionado ao
ViewContainer
. Todas as dependências necessárias são carregadas com ele.
Analisaremos o pacote de aplicativos usando a
webpack-bundle-analyze
fornecida pelo módulo npm correspondente. Permite visualizar o conteúdo dos arquivos que o Webpack produz e examinar o esquema resultante.
Análise de pacote de aplicativosO tamanho do pacote principal do aplicativo é de aproximadamente 260 Kb. Se fizermos o download do componente
QuizCardComponent
junto com ele, o tamanho dos dados baixados será de aproximadamente 270 Kb. Acontece que conseguimos reduzir o tamanho do pacote principal em 10 Kb, carregando apenas um componente no modo lento. Bom resultado!
O código do
QuizCardComponent
após a montagem chega a um arquivo separado. Se você analisar o conteúdo desse arquivo, verifica-se que não há apenas o código
QuizCardComponent
, mas também os módulos de material usados neste componente.
Embora
QuizCardComponent
use
MatButtonModule
e
MatCardModule
, apenas
MatCardModule
entra no
MatCardModule
código finalizado. A razão para isso é que também usamos o
MatButtonModule
no
AppModule
, exibindo o botão
Start quiz
. Como resultado, o código correspondente cai em outro fragmento do pacote.
Agora organizamos o carregamento lento do
QuizCardComponent
. Este componente exibe um cartão, projetado no estilo Material, contendo uma imagem, uma pergunta e botões com opções de resposta. O que acontece agora se você clicar em um desses botões?
O botão, dependendo de conter a resposta certa ou errada, fica verde ou vermelho quando pressionado. O que mais está acontecendo? Nada. E precisamos que, depois de responder uma pergunta, um cartão de outra pergunta seja exibido. Corrija.
Manipulação de eventos
O aplicativo não mostra um novo cartão de pergunta quando você clica no botão de resposta devido ao fato de ainda não termos configurado mecanismos para responder a eventos de componentes carregados no modo lento. Já sabemos que o componente
QuizCardComponent
gera eventos usando o
EventEmitter
. Se você olhar para a definição da classe
EventEmitter
, poderá descobrir que ela é descendente de
Subject
:
export declare class EventEmitter<T extends any> extends Subject<T>
Isso significa que o
EventEmitter
possui um método de
subscribe
, que permite configurar a resposta do sistema aos eventos que ocorrem:
instance.questionAnswered.pipe( takeUntil(instance.destroy$) ).subscribe(() => this.showNewQuestion());
Aqui,
showNextQuestion
o fluxo
questionAnswered
e chamamos o método
showNextQuestion
, que executa a lógica
lazyLoadQuizCard
:
async showNewQuestion() { this.lazyLoadQuizCard(); }
A construção
takeUntil(instance.destroy$)
é necessária para limpar a assinatura executada após a destruição do componente. Se o gancho
ngOnDestroy
do ciclo de vida do componente
ngOnDestroy
for chamado,
destroy$
será chamado com
next
e
complete
.
Como o componente já está carregado, o sistema não executa uma solicitação HTTP adicional. Utilizamos o conteúdo do fragmento de código que já possuímos, criamos um novo componente e o colocamos no contêiner.
Ganchos do ciclo de vida do componente
Quase todos os ganchos do ciclo de vida do componente são chamados automaticamente ao trabalhar com o componente
QuizCardComponent
usando a técnica de carregamento lento. Mas um gancho não é suficiente. Você consegue entender qual?
Ganchos do ciclo de vida do componenteEste é o
ngOnChanges
mais importante do
ngOnChanges
. Como nós mesmos atualizamos as propriedades de entrada da instância do componente, também somos responsáveis por chamar o gancho do ciclo de vida do
ngOnChanges
.
Para chamar o gancho
ngOnChanges
da instância do
ngOnChanges
, você precisa construir um objeto
SimpleChange
:
(instance as any).ngOnChanges({ question: new SimpleChange(null, instance.question, true) });
Chamamos manualmente
ngOnChanges
instância
ngOnChanges
componente e passamos a ela um objeto
SimpleChange
. Este objeto indica que essa alteração é a primeira, que o valor anterior é
null
e que o valor atual é uma pergunta.
Ótimo! Carregamos o componente com módulos de terceiros, respondemos aos eventos gerados e configuramos a chamada dos ganchos necessários do ciclo de vida do componente.
Aqui está o código fonte do projeto finalizado em que estávamos trabalhando aqui.
Sumário
O carregamento lento de componentes oferece aos desenvolvedores Angular grandes oportunidades para otimizar o desempenho do aplicativo. À sua disposição estão as ferramentas para ajustar muito bem a composição dos materiais carregados no modo preguiçoso. Anteriormente, quando era possível carregar apenas rotas no modo lento, não tínhamos essa precisão.
Infelizmente, ao usar módulos de terceiros no componente, também precisamos cuidar dos módulos. No entanto, vale lembrar que no futuro isso poderá mudar.
O mecanismo Ivy introduziu o conceito de localidade, graças ao qual os componentes podem existir independentemente. Essa mudança é a base do futuro da Angular.
Caros leitores! Você planeja usar a técnica de carregamento lento de componentes em seus projetos Angular?
