É 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?
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
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
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.
Artigo muito bom…
…objetivo, claro e fácil entendimento.
Parabéns!!!
Parabéns, muito bom!!!!
Valeu demaisssss!!!
Gostei do artigo, ajudou muito. Parabéns!
Valeu, Luciano!
Ótimo artigo, estou começando agora com Angular, e me deparei com essa premissa sem entender direito do que se tratava, agora tudo faz sentido, obrigado por compartilhar o conhecimento…
Show demais! Depois que se entende um conceito, fica até mais fácil aprender o mesmo em outros frameworks.
P.S. Acredito que você vai gostar bastante do Angular, é bem gostoso de se trabalhar com ele. xD
Ótimo artigo, parabéns!
Obrigado, jovem!
Referências:
Angular DOC
Malcoded
Pusher
Ultimate Courses
Angular University
Loiane Groner Youtube