Angular 9 e Ivy: carregamento lento de componentes

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 Ivy

Na 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 Demo

As 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 QuizCardComponent

Depois 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 componentes

Depois 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 aplicativos

O 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 componente

Este é 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?

Source: https://habr.com/ru/post/pt484618/


All Articles