
Este documento contém uma lista de práticas que nos ajudarão a aumentar o desempenho de nossas aplicações angulares. A lista de verificação de "desempenho angular" aborda diferentes tópicos-desde a pré-renderização e o agrupamento do servidor de nossos aplicativos até o desempenho do tempo de execução e a otimização da detecção de alterações realizada pela estrutura.
O documento é dividido em duas seções principais:
Algumas práticas afetam as duas categorias para que possa haver uma ligeira interseção, no entanto, as diferenças nos casos de uso e as implicações serão explicitamente mencionadas.
A maioria das subseções lista as ferramentas, relacionadas à prática específica, que podem nos tornar mais eficientes, automatizando nosso fluxo de desenvolvimento.
Observe que a maioria das práticas é válida para HTTP/1.1 e HTTP/2. As práticas que fazem uma exceção serão mencionadas especificando a qual versão do protocolo eles poderiam ser aplicados.
enableProdModeChangeDetectionStrategy.OnPush*ngFor DiretivatrackByAlgumas das ferramentas desta seção ainda estão em desenvolvimento e estão sujeitas a alterações. A equipe Core Angular está trabalhando na automação do processo de construção para nossos aplicativos o máximo possível, para que muitas coisas aconteçam de forma transparente.
O Bundling é uma prática padrão com o objetivo de reduzir o número de solicitações que o navegador precisa executar para fornecer o aplicativo solicitado pelo usuário. Em essência, o Putentler recebe como entrada uma lista de pontos de entrada e produz um ou mais pacotes. Dessa forma, o navegador pode obter todo o aplicativo executando apenas algumas solicitações, em vez de solicitar cada recurso individual separadamente.
À medida que seu aplicativo cresce agrupando tudo em um único pacote grande seria novamente contraproducente. Explore as técnicas de divisão de código usando o webpack.
Solicitações HTTP adicionais não serão uma preocupação com o HTTP/2 devido ao recurso Push do servidor.
Ferramentas
As ferramentas que nos permitem agrupar nossos aplicativos com eficiência são:
Recursos
Essas práticas nos permitem minimizar o consumo de largura de banda, reduzindo a carga útil de nossa inscrição.
Ferramentas
Recursos
Embora não vejamos o personagem WhiteSpace (um personagem que corresponde s regex), ele ainda é representado por bytes que são transferidos pela rede. Se reduzirmos o espaço em branco de nossos modelos para o mínimo, seremos, respectivamente, capazes de soltar o tamanho do pacote do código AOT ainda mais.
Felizmente, não precisamos fazer isso manualmente. A interface ComponentMetadata fornece os espaços da propriedade preserveWhitespaces que, por padrão, têm valor false , significando que, por padrão, o compilador angular reduzirá os espaços em branco para reduzir ainda mais o tamanho de nosso aplicativo. Caso definamos a propriedade como true Angular preservará o espaço em branco.
Para a versão final de nossos aplicativos, geralmente não usamos o código inteiro fornecido pela Biblioteca Angular e/ou de terceiros, mesmo a que escrevemos. Graças à natureza estática dos módulos ES2015, podemos se livrar do código que não é referenciado em nossos aplicativos.
Exemplo
// foo.js
export foo = ( ) => 'foo' ;
export bar = ( ) => 'bar' ;
// app.js
import { foo } from './foo' ;
console . log ( foo ( ) ) ; Depois que o Shake e o Bundle app.js vamos obter:
let foo = ( ) => 'foo' ;
console . log ( foo ( ) ) ; Isso significa que a bar de exportação não utilizada não será incluída no pacote final.
Ferramentas
NOTA: O GCC ainda não suporta export * , essencial para a construção de aplicações angulares devido ao uso pesado do padrão "barril".
Recursos
Desde o lançamento da versão 6 Angular, a equipe Angular forneceu um novo recurso para permitir que os serviços sejam de forma de árvore, o que significa que seus serviços não serão incluídos no pacote final, a menos que estejam sendo usados por outros serviços ou componentes. Isso pode ajudar a reduzir o tamanho do pacote removendo o código não utilizado do pacote.
Você pode tornar seus serviços em forma de árvore usando o atributo providedIn para definir onde o serviço deve ser inicializado ao usar o decorador @Injectable() . Em seguida, você deve removê -lo do atributo dos providers da sua declaração NgModule , bem como de sua declaração de importação da seguinte forma.
Antes:
// app.module.ts
import { NgModule } from '@angular/core'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import { environment } from '../environments/environment'
import { MyService } from './app.service'
@ NgModule ( {
declarations : [
AppComponent
] ,
imports : [
...
] ,
providers : [ MyService ] ,
bootstrap : [ AppComponent ]
} )
export class AppModule { } // my-service.service.ts
import { Injectable } from '@angular/core'
@ Injectable ( )
export class MyService { }Depois:
// app.module.ts
import { NgModule } from '@angular/core'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import { environment } from '../environments/environment'
@ NgModule ( {
declarations : [
AppComponent
] ,
imports : [
...
] ,
providers : [ ] ,
bootstrap : [ AppComponent ]
} )
export class AppModule { } // my-service.service.ts
import { Injectable } from '@angular/core'
@ Injectable ( {
providedIn : 'root'
} )
export class MyService { } Se MyService não for injetado em nenhum componente/serviço, ele não será incluído no pacote.
Recursos
Um desafio para os disponíveis nas ferramentas selvagens (como GCC, Rollup etc.) são os modelos do tipo HTML dos componentes angulares, que não podem ser analisados com suas capacidades. Isso torna seu suporte de árvores menos eficiente porque não tem certeza de quais diretrizes são referenciadas nos modelos. O compilador AOT transpiliza os modelos angulares do tipo HTML para JavaScript ou TypeScript com as importações de módulos ES2015. Dessa forma, somos capazes de um shake de árvore com eficiência durante o agrupamento e remover todas as diretrizes não utilizadas definidas por bibliotecas angulares e de terceiros ou por nós mesmos.
Recursos
Compressão da prática padrão da carga útil das respostas para redução de uso de largura de banda. Ao especificar o valor do Accept-Encoding do cabeçalho, o navegador sugere o servidor cujos algoritmos de compactação estão disponíveis na máquina do cliente. Por outro lado, o servidor define o valor para o cabeçalho Content-Encoding da resposta para informar ao navegador qual algoritmo foi escolhido para comprimir a resposta.
Ferramentas
A ferramenta aqui não é específica angular e depende totalmente do servidor da Web/Aplicativo que estamos usando. Os algoritmos de compressão típicos são:
Recursos
A pré-busca de recursos é uma ótima maneira de melhorar a experiência do usuário. Podemos pré-buscar ativos (imagens, estilos, módulos destinados a serem carregados preguiçosamente etc.) ou dados. Existem diferentes estratégias de pré-busca, mas a maioria delas depende das especificidades do aplicativo.
Caso o aplicativo de destino tenha uma enorme base de código com centenas de dependências, as práticas listadas acima podem não nos ajudar a reduzir o pacote para um tamanho razoável (razoável pode ser de 100k ou 2m, depende completamente das metas de negócios).
Nesses casos, uma boa solução pode ser carregar alguns dos módulos do aplicativo preguiçosamente. Por exemplo, vamos supor que estamos construindo um sistema de comércio eletrônico. Nesse caso, podemos querer carregar o painel de administração independentemente da interface do usuário voltada para o usuário. Depois que o administrador precisar adicionar um novo produto, queremos fornecer a interface do usuário necessária para isso. Isso pode ser apenas a "Página do Produto Adicionar" ou todo o painel de administração, dependendo de nossos requisitos de uso/negócio de uso.
Ferramentas
Suponhamos que tenhamos a seguinte configuração de roteamento:
// Bad practice
const routes : Routes = [
{ path : '' , redirectTo : '/dashboard' , pathMatch : 'full' } ,
{ path : 'dashboard' , loadChildren : ( ) => import ( './dashboard.module' ) . then ( mod => mod . DashboardModule ) } ,
{ path : 'heroes' , loadChildren : ( ) => import ( './heroes.module' ) . then ( mod => mod . HeroesModule ) }
] ; Na primeira vez em que o usuário abre o aplicativo usando o URL: https://example.com/, eles serão redirecionados para /dashboard que acionará o painel de rota preguiçoso com o Path dashboard . Para que o Angular renderize o componente de bootstrap do módulo, ele precisará baixar o arquivo do arquivo dashboard.module e todas as suas dependências. Posteriormente, o arquivo precisa ser analisado pela VM JavaScript e avaliado.
Acionar solicitações extras HTTP e executar cálculos desnecessários durante a carga inicial da página é uma prática ruim, pois diminui a renderização inicial da página. Considere declarar a rota da página padrão como não preguiçosa.
O cache é outra prática comum que pretende acelerar nossa aplicação, aproveitando a heurística de que, se um recurso foi solicitado recentemente, poderá ser solicitado novamente em um futuro próximo.
Para dados de cache, geralmente usamos um mecanismo de cache personalizado. Para os ativos estáticos em cache, podemos usar o cache de navegador padrão ou trabalhadores de serviço com a API de CacheStorage.
Para tornar o desempenho percebido do seu aplicativo mais rapidamente, use um shell de aplicativo.
O shell do aplicativo é a interface mínima do usuário que mostramos aos usuários para indicar que o aplicativo será entregue em breve. Para gerar um shell de aplicativo dinamicamente, você pode usar o Angular Universal com diretivas personalizadas que mostram condicionalmente elementos, dependendo da plataforma de renderização usada (ou seja, oculte tudo, exceto o shell do aplicativo ao usar platform-server ).
Ferramentas
Recursos
Podemos pensar no trabalhador de serviço como um proxy HTTP, localizado no navegador. Todas as solicitações enviadas do cliente são interceptadas pela primeira vez pelo trabalhador do serviço, que podem lidar com eles ou passá -los pela rede.
Você pode adicionar um trabalhador de serviço ao seu projeto angular executando ng add @angular/pwa
Ferramentas
Recursos
Esta seção inclui práticas que podem ser aplicadas para fornecer uma experiência mais suave do usuário com 60 quadros por segundo (FPS).
enableProdModeNo modo de desenvolvimento, o Angular realiza algumas verificações extras para verificar se a execução de detecção de alterações não resulta em nenhuma alteração adicional em nenhuma das ligações. Dessa forma, as estruturas garantem que o fluxo de dados unidirecional tenha sido seguido.
Para desativar essas mudanças para a produção, não se esqueça de invocar enableProdMode :
import { enableProdMode } from '@angular/core' ;
if ( ENV === 'production' ) {
enableProdMode ( ) ;
}O AOT pode ser útil não apenas para alcançar um agrupamento mais eficiente, realizando o troca de árvores, mas também para melhorar o desempenho do tempo de execução de nossos aplicativos. A alternativa do AOT é a compilação just-in-time (JIT), que é realizada em tempo de execução; portanto, podemos reduzir a quantidade de cálculos necessários para a renderização de nosso aplicativo, executando a compilação como parte do nosso processo de construção.
Ferramentas
ng serve --prodRecursos
O problema usual no aplicativo típico de página única (SPA) é que nosso código geralmente é executado em um único thread. Isso significa que, se queremos alcançar a experiência suave do usuário com 60fps, temos no máximo 16ms para execução entre os quadros individuais estão sendo renderizados; caso contrário, eles cairão pela metade.
Em aplicações complexas com uma enorme árvore de componentes, onde a detecção de alterações precisa executar milhões de verificações a cada segundo, não será difícil começar a soltar quadros. Graças ao agnosticismo do Angular e à desacoplação da arquitetura DOM, é possível executar todo o nosso aplicativo (incluindo detecção de alterações) em um trabalhador da web e deixar o tópico principal da interface do usuário responsável apenas pela renderização.
Ferramentas
Recursos
Uma grande questão do spa tradicional é que eles não podem ser renderizados até que todo o JavaScript necessário para a renderização inicial esteja disponível. Isso leva a dois grandes problemas:
A renderização do lado do servidor resolve esse problema pré-renderizando a página solicitada no servidor e fornecendo a marcação da página renderizada durante o carregamento inicial da página.
Ferramentas
Recursos
Em cada evento assíncrono, o Angular realiza a detecção de alterações em toda a árvore componente. Embora o código que detecte alterações seja otimizado para cache em linha, isso ainda pode ser um cálculo pesado em aplicações complexas. Uma maneira de melhorar o desempenho da detecção de alterações é não executá -lo para subárvores que não devem ser alteradas com base nas ações recentes.
ChangeDetectionStrategy.OnPush A estratégia de detecção de alterações OnPush nos permite desativar o mecanismo de detecção de alterações para subáridas da árvore componente. Ao definir a estratégia de detecção de alteração para qualquer componente para o valor ChangeDetectionStrategy.OnPush . O Angular considerará os insumos como diferentes quando os compararem com as entradas anteriores por referência, e o resultado da verificação de referência é false . Em combinação com estruturas de dados imutáveis, OnPush pode trazer ótimas implicações de desempenho para esses componentes "puros".
Recursos
Outra maneira de implementar um mecanismo de detecção de alterações personalizadas é o detach e reattach do detector de mudança (CD) para um componente. Depois de detach o CD Angular não executará a verificação de toda a subárvore do componente.
Essa prática é normalmente usada quando as ações ou interações do usuário com serviços externos acionam a detecção de alterações com mais frequência do que o necessário. Nesses casos, podemos querer considerar o destacamento do detector de alterações e se recolocam apenas ao executar a detecção de alterações.
O mecanismo de detecção de mudança do Angular está sendo desencadeado graças ao zone.js. Zone.js Monkey patches todas as APIs assíncronas no navegador e desencadeia a detecção de alterações no final da execução de qualquer retorno de chamada assíncrono. Em casos raros , podemos querer que o código fornecido seja executado fora do contexto da zona angular e, portanto, sem a execução do mecanismo de detecção de alterações. Nesses casos, podemos usar o método runOutsideAngular da instância NgZone .
Exemplo
No trecho abaixo, você pode ver um exemplo para um componente que usa essa prática. Quando o método _incrementPoints for chamado, o componente iniciará a incrementação da propriedade _points a cada 10ms (por padrão). A incremento fará com que a ilusão de uma animação. Como neste caso, não queremos acionar o mecanismo de detecção de alterações para toda a árvore de componentes, a cada 10ms, podemos executar _incrementPoints fora do contexto da zona do Angular e atualizar o DOM manualmente (consulte o setter points ).
@ Component ( {
template : '<span #label></span>'
} )
class PointAnimationComponent {
@ Input ( ) duration = 1000 ;
@ Input ( ) stepDuration = 10 ;
@ ViewChild ( 'label' ) label : ElementRef ;
@ Input ( ) set points ( val : number ) {
this . _points = val ;
if ( this . label ) {
this . label . nativeElement . innerText = this . _pipe . transform ( this . points , '1.0-0' ) ;
}
}
get points ( ) {
return this . _points ;
}
private _incrementInterval : any ;
private _points : number = 0 ;
constructor ( private _ngZone : NgZone , private _pipe : DecimalPipe ) { }
ngOnChanges ( changes : any ) {
const change = changes . points ;
if ( ! change ) {
return ;
}
if ( typeof change . previousValue !== 'number' ) {
this . points = change . currentValue ;
} else {
this . points = change . previousValue ;
this . _ngZone . runOutsideAngular ( ( ) => {
this . _incrementPoints ( change . currentValue ) ;
} ) ;
}
}
private _incrementPoints ( newVal : number ) {
const diff = newVal - this . points ;
const step = this . stepDuration * ( diff / this . duration ) ;
const initialPoints = this . points ;
this . _incrementInterval = setInterval ( ( ) => {
let nextPoints = Math . ceil ( initialPoints + diff ) ;
if ( this . points >= nextPoints ) {
this . points = initialPoints + diff ;
clearInterval ( this . _incrementInterval ) ;
} else {
this . points += step ;
}
} , this . stepDuration ) ;
}
}Aviso : use essa prática com muito cuidado apenas quando tiver certeza do que está fazendo, porque, se não for usado corretamente, pode levar a um estado inconsistente do DOM. Além disso, observe que o código acima não será executado nos webworkers. Para torná-lo compatível com o WebWorker, você precisa definir o valor da etiqueta usando o renderizador do Angular.
O Angular usa o Zone.js para interceptar eventos que ocorreram no aplicativo e executa uma detecção de alteração automaticamente. Por padrão, isso acontece quando a fila do microtask do navegador está vazia, o que em alguns casos pode chamar ciclos redundantes. Do V9, o Angular fornece uma maneira de coalesce a detecção de mudanças de evento, ligando ngZoneEventCoalescing , ou seja,
platformBrowser ( )
. bootstrapModule ( AppModule , { ngZoneEventCoalescing : true } ) ; A configuração acima agendará a detecção de alterações com requestAnimationFrame , em vez de conectar -se à fila do microtosk, que executará verifica com menos frequência e consumirá menos ciclos computacionais.
AVISO: NGZONEEVENTCOALESCING: True pode quebrar aplicativos existentes que transmitem a detecção de alterações consistentemente em execução.
Recursos
Como argumento, o @Pipe Decorator aceita um objeto literal com o seguinte formato:
interface PipeMetadata {
name : string ;
pure : boolean ;
}A bandeira pura indica que o tubo não depende de nenhum estado global e não produz efeitos colaterais. Isso significa que o tubo retornará a mesma saída quando chamado com a mesma entrada. Dessa forma, o Angular pode armazenar em cache as saídas para todos os parâmetros de entrada com os quais o tubo foi invocado e reutilizá -las para não precisar recompensá -las em cada avaliação.
O valor padrão da propriedade pure é true .
*ngFor Diretiva A diretiva *ngFor é usada para renderizar uma coleção.
trackBy Por padrão *ngFor identifica o objeto exclusivo por referência.
O que significa que quando um desenvolvedor quebra a referência ao objeto durante a atualização do conteúdo do item O Angular o trata como remoção do objeto antigo e adição do novo objeto. Isso efeitos na destruição do nó DOM antigo na lista e na adição de um novo nó DOM em seu lugar.
O desenvolvedor pode fornecer uma dica para o Angular Como identificar a singularidade do objeto: função de rastreamento personalizada como a opção trackBy para a diretiva *ngFor . A função de rastreamento leva dois argumentos: index e item . O Angular usa o valor retornado da função de rastreamento para rastrear a identidade dos itens. É muito comum usar o ID do registro específico como a chave exclusiva.
Exemplo
@ Component ( {
selector : 'yt-feed' ,
template : `
<h1>Your video feed</h1>
<yt-player *ngFor="let video of feed; trackBy: trackById" [video]="video"></yt-player>
`
} )
export class YtFeedComponent {
feed = [
{
id : 3849 , // note "id" field, we refer to it in "trackById" function
title : "Angular in 60 minutes" ,
url : "http://youtube.com/ng2-in-60-min" ,
likes : "29345"
} ,
// ...
] ;
trackById ( index , item ) {
return item . id ;
}
} Renderizar os elementos DOM geralmente é a operação mais cara ao adicionar elementos à interface do usuário. O trabalho principal geralmente é causado pela inserção do elemento no DOM e na aplicação dos estilos. Se *ngFor para renderizar muitos elementos, os navegadores (especialmente os mais antigos) podem desacelerar e precisar de mais tempo para terminar a renderização de todos os elementos. Isso não é específico para angular.
Para reduzir o tempo de renderização, tente o seguinte:
*ngFor do seu modelo. Geralmente, os elementos DOM desnecessários/não utilizados surgem de estender o modelo repetidamente. Repensar sua estrutura provavelmente facilita muito as coisas.ng-container sempre que possívelRecursos
*ngForAngular executa expressões de modelo após cada ciclo de detecção de alterações. Os ciclos de detecção de alterações são desencadeados por muitas atividades assíncronas, como resoluções de promessas, resultados de HTTP, eventos do timer, tensões e movimentos de mouse.
As expressões devem terminar rapidamente ou a experiência do usuário pode arrastar, especialmente em dispositivos mais lentos. Considere os valores de cache quando o cálculo for caro.
Recursos
A lista de práticas evoluirá dinamicamente ao longo do tempo com práticas novas/atualizadas. Caso você note algo faltando ou pense que alguma das práticas pode ser melhorada, não hesite em demitir um problema e/ou um PR. Para mais informações, consulte a seção "contribuindo" abaixo.
Caso você note que algo faltando, incompleto ou incorreto, uma solicitação de tração será muito apreciada. Para discussão de práticas que não estão incluídas no documento, abra um problema.
Mit