Lazy Loading Angular - Turbine o Desempenho da sua Aplicação

Angular

Lazy Loading Angular - Turbine o Desempenho da sua Aplicação

Gabriel Nascimento
Escrito por Gabriel Nascimento em 17 de abril de 2020

É surpreendente como o Angular consegue ter o tamanho do arquivo tão pesado…

Por conta disso é comum ver uma Aplicação Web demorar mais de 10 segundos para carregar.

Assim você pode até ir pegar um café, que ainda estará carregando quando voltar…

Para resolver esse problema, você precisa “fatiar” sua aplicação em pequenas partes com ajuda do Angular Lazy Loading.

Então continue lendo esse artigo para descobrir como usar esse recurso incrível e melhorar o desempenho do seu programa.

O que é Angular Lazy Loading e porque é importante?

Uma preguiça representando o Angular Lazy Loading

O Angular faz o download do seu código inteiro de uma só vez no navegador. Esse é seu comportamento padrão, chamado de Eager Loading (ou carregamento “ansioso”).

Lembra dos vários módulos que você projetou?

Exato, ou ele baixa todos os módulos, ou não baixa nada…

E antes que perceba, você encontra problemas de lentidão e dificuldades no uso do aplicativo.

Porém você pode trocar o comportamento padrão do Angular pelo Lazy Loading (ou carregamento “preguiçoso”).

Onde você divide seu código em partes menores. Para que a parte mais importante seja carregada primeiro e depois todas as outras secundárias são carregadas sob demanda.

Assim vamos baixar apenas uma fração do pacote do aplicativo em vez do pacote inteiro. O que ajuda a melhorar seu desempenho.

Principalmente para os usuários que usam dados móveis ou conexões de Internet muito lentas. (ou seja, todo o Brasil haha)

E como implementar essa função incrível?

Como funciona o Angular Lazy Loading e criar sua estrutura inicial

O Angular já possui uma boa divisão de código por conta de sua arquitetura em módulos.

Em que o aplicativo possui um módulo central chamado AppModule, que importa todos os módulos secundários dentro dele. Também chamados de módulos filhos ou módulos de recurso.

E como o AppModule é empacotado na compilação e enviado ao navegador, todos os módulos filhos importados dentro dele vão junto. (ou seja, a aplicação inteira)

Então para implementar o Lazy Loading, o primeiro passo é separar os módulos filhos do AppModule.

Para isso vamos criar um módulo filho com o seguinte comando do Angular CLI:

$ ng g module shop

E depois criar 3 componentes dentro do nosso novo módulo Shop, ou ShopModule:

$ ng g c shop/cart
$ ng g c shop/checkout
$ ng g c shop/confirm

Lembre de verificar se o seu AppModule não está importando o ShopModule.

Porque toda configuração do Lazy Loading não serve de nada caso você continue a importar os módulos filhos que nem um idiota. E o ShopModule volta a carregar com “ansiedade”.

Se a importação foi feita, então comente ou apague o código no seu arquivo app.module.ts dessa maneira:

import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { HomeComponent } from './home/home.component';
//import { ShopModule } from './shop/shop.module';

@NgModule({
  imports: [
    AppRoutingModule
    //ShopModule
  ],
  declarations: [AppComponent, HomeComponent],
  providers: [],
  bootstrap: [AppComponent]
})

export class AppModule { }

Assim o ShopModule fica de fora quando baixar o AppModule.

E como pedir ao Angular para trazer o ShopModule?

Configuração do loadChildren na rota principal

Criação de rotas para Angular Lazy Loading

Mesmo no Lazy Loading, ainda solicitamos os recursos dos módulos filhos através do RouterModule.

Porém você deve usar a propriedade mágica loadChildren nas definições de rotas ao invés do tradicional component.

No módulo de roteamento abaixo, demonstramos os 2 tipos onde:

  • O componente Home é “normal”, carregado por Eager Loading;
  • E o módulo Shop é “preguiçoso”, carregado por Lazy Loading.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './home/home.component';

export const routes: Routes = [
  { path: '', component: HomeComponent, pathMatch: 'full' },
  { path: 'shop', loadChildren: () => import('./shop/shop.module').then(m => m.ShopModule) }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})

export class AppRoutingModule { }

Mas espera! Porque o ShopModule é importado dentro do loadChildren?

Não é proibido importar módulos “preguiçosos”?

Sim, porém note que a função import está dentro de uma Arrow Function ( () => )…

Então ela só importa quando a Arrow Function for executada.

Assim o Angular realiza uma importação dinâmica e executa o import apenas quando necessário.

Como você pode ver, a importação dinâmica é baseada em Promise e fornece acesso ao módulo.

Configuração das rotas no módulo filho

Como o módulo filho está isolado do restante da aplicação, ele é responsável pelo roteamento dos seus componentes. Assim o módulo fica independente por completo.

Então tudo o que falta é configurar as rotas específicas de cada componente do ShopModule

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { CartComponent } from './cart/cart.component';
import { CheckoutComponent } from './checkout/checkout.component';
import { ConfirmComponent } from './confirm/confirm.component';

export const routes: Routes = [
  { path: '', component: CartComponent },
  { path: 'checkout', component: CheckoutComponent },
  { path: 'confirm', component: ConfirmComponent }
];

@NgModule({
  imports: [
    RouterModule.forChild(routes)
  ],
  declarations: [CartComponent, CheckoutComponent, ConfirmComponent]
})

export class ShopModule { }

Observe que incluímos as rotas com o método forChild do RouterModule em vez de forRoot:

imports: [
  RouterModule.forChild(routes)
]

Além disso usamos um path vazio, e outros 2 com apenas o caminho relativo:

export const routes: Routes = [
  { path: '', component: CartComponent },
  { path: 'checkout', component: CheckoutComponent },
  { path: 'confirm', component: ConfirmComponent }
];

Porque esse módulo é usado em conjunto com loadChildren e path do módulo central, assim o AppModule é quem dita a URL.

Então criamos uma estrutura de módulo flexível, na qual seus módulos filhos “desconhecem” seu caminho absoluto. E eles se tornam caminhos relativos com base no AppModule.

Portanto os caminhos da nossa aplicação ficam:

  • /shop para carregar o CartComponent;
  • /shop/checkout para carregar o CheckoutComponent;
  • /shop/confirm para carregar o ConfirmComponent.

E o módulo Shop apenas será carregado quando um desses caminhos for navegado pela primeira vez.

Perceba também que podemos “mover” um módulo filho inteiro sob um novo caminho de rota (por exemplo, se mudarmos o /shop para /cart) e tudo funcionará como planejado.

Aviso: Sempre que configurar um Lazy Loading, você deve reiniciar o servidor. Pare o “ng serve” e depois execute novamente.

Resultados finais ao implementar o Angular Lazy Loading

Vamos imaginar que temos carregada nossa aplicação:

vendor.js [200kb] //angular, rxjs, etc
app.js [300kb] //main app bundle

Ao separar o ShopModule do app.js através do Lazy Loading, teremos um resultado parecido com:

vendor.js [200kb] //angular, rxjs, etc
app.js [200kb] //main app bundle
0-chunk.js [100kb]

Em que 0-chunk.js é um “pedaço” da nossa aplicação, carregado depois do app.js quando solicitado. Se tivéssemos 2 “pedaços”, seria 0-chunk.js e 1-chunk.js.

E assim por diante com 3, 4, 5 “pedaços”…

Por fim é provável que você não note um aumento no desempenho em um exemplo tão simples como esse. Porém há muitos benefícios quando for um aplicativo maior com uma boa base de código.

Opção alternativa: Pré-carregamento assíncrono dos módulos

Robôs atirando para todos os lados

Outra opção de configuração é o recurso PreloadAllModules, que busca todos os módulos restantes antes mesmo do usuário solicitar.

Então após inicializar a aplicação, ele antecipa o carregamento dos módulos filhos de maneira assíncrona e escondida.

Assim caso algum dos módulos seja solicitado, já estará presente no navegador. Levando ao acesso mais rápido.

Um recurso ótimo para o desempenho da aplicação e terrível para pessoas com pacotes limitados de dados móveis.

Então avalie bem qual sua situação antes de implementar o PreloadAllModules.

Você pode realizar essa configuração na função forRoot do RouterModule, dessa maneira:

import { NgModule } from '@angular/core';
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';

import { HomeComponent } from './home/home.component';

export const routes: Routes = [
  { path: '', component: HomeComponent, pathMatch: 'full' },
  { path: 'shop', loadChildren: () => import('./shop/shop.module').then(m => m.ShopModule) }
];

@NgModule({
  imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })],
  exports: [RouterModule]
})

export class AppRoutingModule { }

Parabéns por ter chegado até aqui!

Talvez não seja óbvio o uso do Lazy Loading para projetos pequenos…

Mas se você criar sua aplicação pensando desde o inicio em estruturar módulos bem divididos (capazes de implementar o carregamento “preguiçoso” caso necessário) vai conquistar um projeto mais flexível e escalável.

Além de um código mais limpo…

Então espero ter ajudado você a acrescentar mais essa arma ao seu Arsenal de Programador.

E caso tenha gostado desse artigo, ou tem alguma dúvida, não esqueça de deixar nos comentários abaixo:

Hey,

o que você achou deste conteúdo? Conte nos comentários.

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

3 Replies to “Lazy Loading Angular – Turbine o Desempenho da sua Aplicação”