Performance em Angular — Dicas e truques

Clayton K. N. Passos
codigorefinado
Published in
23 min readSep 23, 2018

--

Liga o som, pega uma pipoca, que este texto é bem rico e interessante. Nele, vamos falar de algumas otimizações, que independem do seu “framework” favorito outras são especificas do Angular, atualmente Angular 6.

Este texto, será alterado constantemente pois há mais e mais pontos de otimização de uma webapp aparecendo todos os dias, aqui estão algumas que tenho experimentado. Se você conhece algum meio de otimizar suas applicações web, por favor, escreva, quero aprender mais sobre o tema.

Desde o começo da sua leitura, mantenha em mente que existem alguns estudos demonstrando que 53% das pessoas que utilizam um dispositivo móvel abandonam o site se ele não carregar em 3 segundos. Este número é importante, pois mostrar algo pro usuário em 3 segundos deveria ser o nosso alvo.

Entenda as limitações do seu cliente

Você sabia que com HTTP 1.x a maioria dos navegadores tem limitações de conexões simultâneas até hoje? Sim, tem! Com SPA você vai acabar abrindo várias conexões, oque vai trazer a percepção de lentidão, pois algumas delas vão ficar esperando ou vão até expirar.

https://stackoverflow.com/questions/985431/max-parallel-http-connections-in-a-browser/985704#985704

Tree shaking

Esta é uma técnica utilizada por muitos compiladores, a ideia base é remover todo código não utilizado (dead code). Para que esta operação seja executada em sua aplicação, você deve compila-la utilizando AoT (Ahead of Time Compilation).

Esta técnica, está constantemente sendo aprimorada no Angular, podemos ver no twitter do Wassim Chegham, que na versão 6 temo o Tree Shaking nas classes @Injectable().

Brad Green Twitou sobre o mesmo tema, explicando como isto funciona, na prática todos queremos que o tree hand shake funcione, logo esta nova forma de declarar um service tende a se tornar o novo padrão.

Mantenha a versão do Angular atualizada

O time que desenvolve este framework está constantemente adicionando melhorias de performance, inclusive, o tamanho dele vem diminuindo enquanto a quantidade de funcionalidades vem aumentando.

Na imagem abaixo, podemos ver como o tamanho do Angular foi diminuindo a cada nova versão.

https://goo.gl/AoZb1n

Abaixo, vemos como uma das bibliotecas utilizadas no Angular, a RxJS liderada por Ben Lesh também vem diminuindo de tamanho.

RxJS 5.5 menor

“Se você não gosta do Angular, tudo bem, pode ficar no seu framework preferido, mas ao menos procure conhecer o RxJS, esta é uma excelente biblioteca que pode te ajudar em cenários complexos em que é necessário compor várias requisições sem perder a sensação de boa performance e sem a necessidade de pensar em multithreads”

A versão 6 o RxJS foi totalmente reescrita, a proposta é que seja ainda menor, e mais performática na execução.

Preloading — Pré carregando com rel=”preload”

Especifica recursos que devem ser carregados após o loading, mas antes de serem requisitados pelo cliente, levando a melhorias no desempenho (na execução, não no primeiro carregamento).

Para obter este benefício ao invés de escrever isto:

<link id="theme-css" rel="stylesheet" type="text/css" href="assets/theme/theme-blue.css">

Escreva isto:

<link id="theme-css" rel="preload" type="text/css" href="assets/theme/theme-blue.css">

Que tipos de conteúdo podem ser pré-carregados com link rel="preload"?audio, video, document, fetch, font, image, script, track, worker, embed, object

Pre rendering — Angular Universal

https://www.youtube.com/watch?v=yXjYtBdJDyU&feature=youtu.be&t=32m5s

Server Side Rendering (SSR), ou seja, renderiza a aplicação no servidor, oque pode trazer melhor performance na renderização da aplicação por se valer de um processador melhor que o do cliente, que poderia ser um dispositivo móvel com pouco poder de processamento.

A pré-renderização é apenas no primeiro carregamento da aplicação. Depois disso, sua aplicação Angular se comporta como uma aplicação Angular normal. Em termos de performance, estamos falando que a primeira pagina a ser carregada, tende a ser carregada mais rapidamente com esta estratégia.

Esta tecnologia torna a aplicação visível para bots, como o Google. Dado que os bots não executam JavaScript, precisamos entregar para eles HTML e CSS, formando uma página coesa e com as metatags devidamente formadas, caso contrário, ele teria algo como a imagem abaixo:

Não conseguindo pegar a informação dinâmica referênte ao {{title}} e {{Description}}.

Ao utilizar o Angular Universal, não se esqueça de que não temos um window, document ou jQuery, pois o trabalho não é feito dentro de um navegador, logo não os utilize. Porém, existem alternativas para manipular o DOM, como, ElementRef, Renderer e ViewContainer.

@angular/platform-server

Defer

Quando estiver utilizando SSR (Server Side Rendering) você pode obter benefício ao utilizar defer na tag script. Defer é booleano, quando presente indica que carregar o arquivo .js não deve bloquear a renderização do DOM e que será executado ao terminar de carregar a página (page has finished parsing).

Mas atenção, o Safari mobile, bloqueia a renderização, mesmo utilizando o defer. Também tome cuidado com a palavra reservada async, ao utilizar ela você pode experimentar comportamentos intermitentes, pois os scripts irão carregar assincronamente, junto do resto da página.

https://www.w3schools.com/tags/att_script_defer.asp

CSS também merece atenção

Algumas práticas também podem ser adotas ao escrever seu CSS, veja que o CodeFactor tem a nos oferecer. Esta ferramenta analisa estaticamente o código e tem indicado, na imagem abaixo, que o uso do seletor * é lento no meu bucador de vagas de TI chamado parapasa.

Como não tenho me dedicado meu tempo ao CSS não consigo trazer muito mais detalhes, não sei quanto essa avaliação sobre o seletor * (asterisco) é relevante, mas fica aqui a sugestão de pesquisa, e encontrar oque é lento e como otimizar o seu CSS.

O tamanho do CSS também deve ter atenção, principalmente na primeira no primeiro carregamento (se estiver usando lazyloading). Neste quesito, um plugin para o webpack chamado critical pode ajudar. A ideia do critical é ajudar a criar um CSS e um HTML minimo o suficiente para carregar a primeira pagina, deixando o resto para carregar depois. É claro que você pode fazer isso sem o Critical.

JavaScript

Quando possível, não utilize forEach, existe uma técnica conhecida como borbulhar, você pode utiliza-la para deixar de utilizar o forEach para incluir eventos em vários elementos DOM para incluindo apenas no elemento pai, e utilizando o target para identificar qual dos filho foi clicado.

White space otimization

Utilizando o decorator @Component() podemos dizer que espaços em branco, excedente devem ser removidos, seja em uma frase, seja entre tags html.

Você pode forçar o espaço utilizando: &ngsp;

https://goo.gl/e7rZ8v
https://angular.io/api/core/Component#preserveWhitespaces

RxJS

Encadeamento de requisições

Evite fazer escrever um código, mesmo em RxJS como no primeiro caso, demonstrado na imagem abaixo. Neste exemplo queremos mostrar duas informações do usuário, o perfil e as preferencias, sendo informações do mesmo usuário é possível encadear as requisições, porém isto bloqueia a renderização, fazendo com que o perfil só seja renderizado após a requisição do perfil finalizar.

É melhor escrever como no segundo código, teremos o perfil sendo renderizado primeiro, mesmo antes das preferências terem sido retornas pela requisição, melhorando a percepção de performance.

Evite callbacks ou funçõs desnecessárias

RxJS é útil em vários momentos, não apenas quando precisamos fazer requisições ao servidor para pegar nossos dados, imagine que você está criando um componente de auto complete, inicialmente você pode desejar que a cada letra digitada, seja disparada uma requisição para o servidor com a palavras que esta se formando. Porém, rapidamente, irá perceber que isto trará problemas de performance no front e também sobrecarregando o back.

Para o caso citado a cima, devemos utiliza operador debounceTime, que irá controlar a quantidade de requisições disparadas por um determinado tempo, incluindo um intervalo entre elas.

Outro exemplos de uso deste operador (debounceTime) podem surgir quando precisamos executar algo durante o arrastar do mouse, ou utilizar a barra de rolagem como no caso de implementar um “infinite scroll”.

Ainda sobre callback desnecessário, caso você encontre uma situação, em que o servidor envia os dados e a cada informação recebida é provocada uma atualização na tela, você pode utilizar o operador buffer.

A idéia do operador buffer é acumular os dados recebidos durante um tempo, e quando este tempo expirar, então entregamos pra view atualizar a tela. Desta maneira o navegador utiliza menos processamento renderizando sua pagina.

Vazamento de memória

Vazamento de memória não é exclusividade de um Observable que não teve o unsubscribe devidamente executado. Se houver código JavaScript adicionando listener a um evento, que não foi devidamente removido, também trará este problema.

Abaixo você vê a utilização do ngOnDestroy para realizar o unsubscribe e para remover o listener no momento da destruição do component.

Caso você esqueça de remover um EventListener, o problema pode ser maior que simplesmente consumir e não liberar. Poderá haver comportamentos anômalos na aplicação, por estar executando o listener em lugares que não deveria.

Para o caso de precisar executar o unsubscribe no evento de destruição do componente, você pode utilizar o decorator @AutoUnsubscribe.

Ben Lesh, um dos principais responsáveis pelo RxJS, também já falou sobre o assunto, neste post abaixo:

Escolha o operador mais adequado

Ao realizar requisições a API, escolha o operador mais adequado a sua necessidade.

Utilize, importe apenas os operadores (RxJS) necessários

Não confie que o threeshake irá conseguir remover todo o código não utilizado, quando se trata de um método prototype do RxJS o algorítimo utilizado (webpack) não consegue remover este código do bundle final. Você precisa dar uma ajuda pra esta otimização acontecer.

Importar para o seu código fonte apenas os operadores do RxJS irá diminuir o tamanho do bundle final da sua aplicação, melhorando o tempo de carregamento da mesma.

Para verificar o tamanho do bundler da sua aplicação, você pode utilizar o webpack-bundle-analyzer, para isto execute os comandos abaixo:

$> ng build --prod --stats-json
$> npm install -g webpack-bundle-analyzer
$> webpack-bundle-analyzer dist/stats.json

RxJS 6

Com a chegada do RxJS 6 a maneira de encadeiar os operadores mudou

Agora temos os pipeable operators, o seu código vai ficar um pouco maior em comparação com o RxJS 6, mas o seu bundle final ficará menor, pois com esta nova maneira e encadear os operadores é possível remover os não utilizados de maneira eficiente com o tree shaking.

Ivy Renderer no Angular 7

O Ivy Renderer é um novo mecanismo de renderização que foi projetado para ser compatível com a renderização existente e focado para melhorar a velocidade de renderização e otimiza o tamanho do pacote final. Para o Angular, esse não será o renderizador padrão, mas você pode ativá-lo manualmente nas opções do compilador.

Com o Ivy você pode esperar melhora na velocidade de renderização e redução do tamanho de sua aplicação em até 90%. — “Atualize para o 7 :D”.

AoT — Ahead Of Time

Ao utilizar AOT, você está optando por ter um tempo de build maior, mais tempo de processamento no seu pipe line de integração continua, algumas das coisas que acontecem nesta etapa é a a remoção de código morto, transpilação, minificação do HTML, JavaScript e CSS. Se estiver usando LazyLoading é neste momento que se gera os chunks (pedaços do bundle).

Graças a todo este processamento do seu código, é que também obtemos um código mais seguro, e validações de código que são possíveis apenas no momento da transpilação.

AoT acontece durante a compilação da sua aplicação, para utilizar deve-se explicitamente passar um parâmetro para o Angular-Cli executar o AoT.

ng build — -aot

Para executar o servidor em modo desenvolvimento mas, utilizando a compilação AoT, utilize:

ng serve — — aot

Isto irá produzir um código minificado e sem os “códigos mortos”. Ou seja, irá otimizar a execução e carregamento da aplicação

Na imagem abaixo você pode ver e comparar o tamanho da etapa de Run das duas estratégias, JIT e AOT. Lembre-se que run acontece no navegador do seu usuário, AOT oferece o menor tempo, logo a melhor performance.

https://blog.nrwl.io/angular-is-aot-worth-it-8fa02eaf64d4

Detecção de mudanças

Há ao dois algorítimos de detecção de mudanças no Angular:

  • ChangeDetectionStrategy.Default — O primeiro e padrão, verifica se a o ponteiro do objeto mudou, caso não tenha mudado, entra em cada atributo do objeto e verifica se houve mudança no conteúdo do mesmo. Imagine isso acontecendo um Array do JavaScript com 5 mil elementos, e cada elemento contendo sub-arrays, com sub-objetos. Lento certo?
  • ChangeDetectionStrategy.OnPush O segundo algorítimo, desliga parte do primeiro algorítimo, ele desliga a varredura de atributos dos objetos, verificando apenas o objeto e não a informação contida nele, oque diminui o processamento logo melhorando a performance no momento da renderização da tela. Neste caso, se você alterar o conteúdo de uma objeto, o Angular não vai atualizar o DOM e seu usuário vai te ligar reclamando :D.

OnPush — Immutable

https://facebook.github.io/immutable-js/

Em programação orientada a objetos e funcional, um objeto imutável é um objeto no qual seu estado não pode ser modificado após ser criado.

Objetos imutáveis junto da estratégia de detecção de mudanças OnPush, ajuda o Angular a saber que precisa atualizar ou não o DOM mais rápido que objetos mutáveis, por diminuir a quantidade de verificações necessárias.

Vamos supor que você possuí um Array JavaScript, e precisamos incluir um novo elemento neste Array. Neste caso, o Angular teria de ir até o ultimo elemento para descobrir que há mudanças no array, pois apenas olhar o tamanho dele não nos garante saber que um dos objetos mudou.

Mas se estivermos trabalhando com Imutabilidade, somos obrigados a criar um novo Array, isto significa um novo ponteiro, logo, o Angular consegue saber que o Array mudou, pois para o Array é diferente, ele não precisa entrar em cada elemento para verificar se algum mudou.

Há situações, em que nós deliberadamente queremos que o sistema de detecção de mudanças, pule etapas, ou simplesmente não execute até que algum evento ocorra. Este tipo de situação comumente acontece quando temos um formulário, e depois do seu preenchimento parte da nossa tela é atualizada, na imagem abaixo temos um formulário, que ao pressionar Enter atualiza a lista abaixo.

Neste exemplo, se o formulário estiver utilizando Two Way Data Binding, a cada letra digitada, provocará alteração na variável que armazena o valor, e isto irá provocar a execução do algorítimo padrão de detecção de mudanças.

Para termos o benefício do OnPush, no caso da imagem acima, precisamos dividir a tela em dois componentes, o primeiro contendo o formulário com @Output() para emitir um evento avisando da alteração, e o segundo contendo a definindo a estratégia de detecção de mudanças para OnPush e a lista com @Input() . Neste caso, quando o @Input() for acionado, o Angular saberá que deve executar a detecção para o componente que lista os nomes, não antes, pois as alterações feitas no componente que está o formulário não são visíveis no componentes que lista os itens, eles serão visíveis, somente após a emissão do evento.

O Exemplo acima, é praticamente um padrão, sempre que encontrar situação similar, tenha em mente que você pode estar lidando com uma tela que terá melhoria na performance na renderização ao utilizar OnPush, @Input, @Output e opcionalmente Immutable, digo opcionalmente porque ao utilizar os três primeiros já haverá ganho, com o Immutable o ganho será ainda maior.

ChangeDetectorRef

Ha casos em que é necessário forçar a detecção de mudanças, para estes casos temos um o objeto ChangeDetectorRef que é injetável, nele podemos chamar o método detecChanges para forçar a detecção, além de poder desligar (detach) e liga-lo (reattach).

Flux — ngrx

Se você está pensando em adotar Immutabilidade, e também tem a pretenção de utilizar Memoization, talvez você deva considerar utilizar @ngrx/store em sua aplicação. Ele isola os dados da sua aplicação em um local único, permitindo um ponto central para utilizar o Memoization.

Caso você observe a necessidade de utilizar o padrão arquitetural Flux, que não é para todas as aplicações, verá que seu código tende a ser criado em um estilo diferente, e irá dispensar alguns controles extras de integridade de dados e de detecção de mudanças.

Zone.js — NgZone (com o Ivy provável que isto seja depreciado apartir do Angular 9)

Zone.js provê um mecanismo para encapsular, interceptar e manter atividades assíncronas no navegador.

Zone.js é parte importante do sistema de detecção de mudanças do Angular. Quando utilizamos Observables, ou qualquer evento assíncrono o Zone.js é envolvido no trabalho e dispara o sistema de detecção de mudanças.

O Angular em si funciona sem o Zone.js, mas o sistema de detecção automática de mudanças fica comprometidas, é necessário informar quando o se deve rodar a detecção manualmente.

Use Zone.js para fazer processamento sem o conhecimento do Angular, depois de terminado atribuir o valor final ao controle do Angular para que ele saiba que os valores mudaram, sem esquecer do método para executar a detecção de mudança.

Na imagem abaixo, você pode observar dois trechos de código, o primeiro irá realizar algum processamento sem o conhecimento do zone.js, logo, sem que o Angular rode o mecanismo de detecção de mudanças. Tenho visto esta estratégia sendo utilizada, em momentos que se deseja realizar processamentos grandes e pesados, com diversas interações incrementais, oque faria com o sistema de detecção de mudanças rodasse muitas e muitas vezes, desnecessariamente. Digo desnecessária, pois apenas a última interação é que interessaria para realizar mudanças na tela da aplicação.

O segundo código, demonstra como rodar código dentro do zone.js, logo, permitindo que o Angular decida por si só que deve rodar o sistema de detecção de mudanças.

Async pipe

Cuidado ao utilizar async pipe. Ele é bom, não se engane quando digo que você deve tomar cuidado. Analise o contexto em que você está utilizando-o, pois juntamente com o sistema de detecção de mudanças, ele pode provocar requisições desnecessárias, por ser necessário resolver o observable a cada execução do sistema de detecção de mudanças do Angular.

No vídeo abaixo, demonstro o uso de duas consultas a API, e ambas utilizam async pipe para simplificar o código e fechar automaticamete (unsubscribe) o observable para evitar vazamento de memória, oque realmente acontece.

Porém, observe que aparte de cima, onde você vê 1 Grupo e 2 Propósito faz parte de um componente pai, e a lista abaixo, é um componente filho onde se encontra o pipe async. Veja, que ao interajir com o componente pai, ele provoca atualização no filho, logo, o async pipe precisa resolver novamente, provocando a requisição a API em todas as vezes que clico.

Agora no vídeo abaixo, veja que eu refatorei um os componentes, removendo o async pipe, passando o array de objetos como parâmentro no @Input(), fazendo com que, não provoque novas requisições a API a cada interação do usuário com os links.

No vídeo a baixo, você pode ver os dois componentes filhos refatorados, compare a execução deste código, com o do primeiro, perceba que, este ultimo exemplo, que não utiliza async pipe é perceptivelmente mais rápido que o primeiro que utiliza async pipe nos dois componentes.

Fique atento, ao fato de que:

  • A lentidão neste caso se deve ao fato de que o async pipe precisa resolver a requisição toda vez que o sistema de detecção de mudanças precisa executar e realizar o seu trabalho, já que ele não temos nenhum tipo de cache que impeça isto de acontecer.
  • Que se você utilizar Immutable, no ultimo código, a execução vai melhorar, e se utilizar a estratégia OnPush, melhorará ainda mais, doque você consegue observar atualmente no ultimo vídeo.

Sobrescreva o set de um atributo com @Input()

Está usando @Input() sobrescrevendo o set pra receber o valor que é utilizado pra fazer pesquisas que normalmente se faria no OnInit, mas agora fazendo no set do @Input()?

Sim, você pode criar um set para o atributo que está recebendo como parâmetro, e então utilizar o valor para fazer sua pesquisa, algo, que normalmente se faria no OnInit, agora você faz no set. A vantagem, neste caso, é que, caso o Angular decida executar o ciclo de vida, ele não irá executar novamente a pesquisa. Veja exemplo abaixo, ilustrando uma pesquisa realizada logo no set grupo.

Pipe

Pipes são executados apenas quando há mudança no array users, enquanto que utilizando method bind sempre é executado (CD, Change Detection)

Lodash decorators — @Memoize()

Esta usando cache pra não fazer requisições desnecessárias? É claro que sempre que lidamos com cache, temos de lidar a situação em que o usuário deveria receber uma informação nova, mas está recebendo uma antiga que está em cache, mas quando bem utilizado, cache ajuda muito na melhoria da performance da sua aplicação.

Ao utilizar @memoize do lodash-decorators, ao enviar os mesmo parâmetros para um método, ele não será executado, simplesmente retorna o resultado da chamada anterior.

Vale chamar atenção, de que você não precisa sempre incluir todo o Lodash em sua aplicação, você pode incluir apenas uma função individual. Assim, o tempo de carregamento da sua aplicação fica menor, devido ao fato de que você não está incluindo funções que não utiliza. Por exemplo.

npm i lodash._htmlunescapes

Pipe — Memoize

Você já leu que o memoize serve para fazer um cache em tempo de execução para nós. Caso você tenha criado um pipe, você pode usar memoize para cachear o valor a fim de não repetir a operação, é praticamente certeza de que se o seu pipe for puro, você possa utilizar memoize. Por exemplo, digamos você tem duas informações e quer calcular o terceiro

valorLucro = valorPrevisto — valorGasto;

Programadores Delphi utilizam muito o termo “Campo calculado”, este é um excelente candidato a utilização do pipe para exibição, e do memoize otimização via cache.

Request cache

Você pode querer fazer cache em requisições, a fim de evitar que muitas requisições cheguem ao servidor evitando latência de rede e demora no processamento da requisição.

Uma das maneiras de fazer isto é utilizar o operador shareReplay.

https://dev.to/2muchcoffeecom/rxjs-welcome-on-board-upgraded-sharereplay-operator-428c

Outra maneira é criar um interceptor de requisições pra manter suas requisições em memória

https://github.com/angular/angular/blob/master/aio/content/examples/http/src/app/http-interceptors/caching-interceptor.ts

https://github.com/angular/angular/blob/master/aio/content/examples/http/src/app/request-cache.service.ts

LazyLoading

Está usando LazyLoading? Deveria, com Angular é fácil de utilizar, isto irá fazer com que o usuário tenha uma sensação e que sua aplicação é mais rápida doque se não tivesse isto.

Basicamente, LazyLoading no Angular vai fazer com que os HTML, CSS e alguns JavaScripts seja carregados apenas no momento que forem acessados. Em outras palavras, ao abrir sua aplicação, não será feito o download de tudo para o navegador, algumas partes serão carregadas ao trocar de rota.

Isto no leva a recomendação, faça com que sua aplicação carregue a página de login, de erro e a rota carregada logo após a autenticação. Deixe todo o resto configurado para carregar tardiamente (LazyLoading).

Você também pode, através do ngx-quicklink fazer Preloading de rotas que são LazyLoading.

Está compactando a requisição?

É altamente recomendado configurar seu servidor a compactar as respostas, isto irá fazer com que o dado trafegado na rede seja menor, geralmente :D

Progressive web apps

Service working

Verificou a possibilidade de utilizar de utilizar este recurso (Service working) para previamente carregar informações que você já sabe que o usuário irá precisar em breve?

A idéia de utilizar Service Working é fazer requisições para sua aplicação, a fim de pegar informações que serão necessárias, mas que o usuário ainda não as pediu, logo que recebe-las, você precisará mante-las no navegador, como um cache para serem recuperadas quando for necessário.

Tratamento de imagens

Suas imagens foram tratadas para web? Evite utilizar imagens em alta resolução, oferaça resoluções altas sob demada. Para ajudar, este trabalho existem serviços online, como o cloudinary, que permite armazenar sua imagens, video e outros arquivos, e por meio de uma api você as recupera devidamente processadas.

O processamento de imagens do cloudinary permite coisas como: Redimencionar, adicionar marca d’agua, aplicar filtros de cor, remover cores, clarear, escurecer, criar miniaturas, mesclar, tornar partes transparentes…

API — JSON

Escolheu a estratégia mais otimizada de implementar sua API (json)? HEATEOAS apesar de bonito aumenta a quantidade de requisições, aumentando o tempo de espera para obter os dados. Por outro lado, retornar um JSON gigante, pode aumentar o tempo de processamento do navegador, por tratar um dado grande e com muitos dados inúteis.

Tenho visto HEATEOAS sendo evitado quando se fala de aplicações móveis, devido ao fato de precisar utilizar por mais tempo a conexão de dados, logo, consumindo mais a bateria do aparelho.

Também tenho visto APIs sendo criadas utilizando GraphQL quando dispositivos móveis serão seus clientes, o principal motivador é o menor trafego de rede, e a possibilidade de utilizar o Observable no lugar do Socket no local de notificações. Possívelmente isto implica mais trabalho e planejamento na fase de “design api”.

Microfront end

Por meio do custom elements, empresas estão criando microfront, e isto pode trazer ganhos na granularidade e reaproveitamento dos componentes, porém, fique de olho no tamanho do bundle.

Evite utilizar todos os frameworks possíveis para criar seus componentes. Isto tende a derrubar a performance de sua aplicação no loading devido ao aumento do bundle da aplicação. Apesar do reaproveitamento do componente, esta prática tende a derrubar a produtividade no momento de concepção da aplicação e no momento de encontrar erros, por precisar que os desenvolvedores conheçam todos os frameworks que foram utilizados nos diversos componentes. Para entender melhor como isto acontece, você pode pesquisar pelos 7 ou 8 princípais desperdícios, mais específicamente no tópico de chamado de “Mental queue” ou “Task Switching”.

Source map Explorer

Source map explorer irá te ajudar a identificar um bundler que está grande além do necessário, por exemplo, se o desenvolvedor adicionar todos os operadores do RxJS, pode ser observado e então você pode varrer seu código a procura de operadores para remover.

$> npm install -g source-map-explorer
$> ng build --prod --sourcemaps
source-map-explorer dist/vendor.main.js.map

Outro exemplo de otimização que posso dar como exemplo, é quanto ao uso do Lodash, você pode identificar que está sendo importado todo o Lodash para a sua aplicação, quando na realidade você poderia importar apenas algumas funções individuais, como o lodash.isequal (@types).

lodash.isequal

npm install --save lodash.isequalES5 e CommonJS Module
var isEqual = require('lodash.isequal');
ES6
import isEqual from 'lodash.isequal';

Abaixo, vemos o Lodash, com todos os métodos dentro do projeto, temos aqui uma oportunidade de melhoria no carregamento da aplicação. Também observo diversos componentes do PrimeNG que não utilizo na aplicação, eu poderia simplesmente remover o import de seus módulos.

Veja abaixo as duas imagens, a primeira você encontra a esquerda o datatable.js dentro do primeng, na segunda imagem o datatable.js não existe, pois o removi, esta alteração diminuiu o tamanho do bundle final de 5.94MB para 5.72MB como podemos observar no gráfico gerado pelo webpack-bundle-analyzer.

Gráfico gerado com o webpack-bundle-analyzer para demostrar o tamanho COM o datatable.js do PrimeNG (5.94 MB)
Gráfico gerado com o webpack-bundle-analyzer para demostrar o tamanho SEM o datatable.js do PrimeNG (5.72 MB)

HTTP 2

Apenas configure seu servidor, e obtenha melhor performance. fim :D Brincadeira, oque acontece aqui, é que o protocolo é mais leve, por isso há ganhos ao utiliza-lo, além de que, se o browser não for compatível, ele responderá (ou deveria) utilizando o protocolo HTTP 1.x.

App shell — Application Shell

App shel é a combinação de estratégias utilizadas para limitar oque será carregado na primeira vez que o usuário entrar na sua aplicação web. Ou seja, carregar o menor CSS, HTML, JavaScript e imagens, etc.

Também podemos incluir neste conjunto de estratégias, utilizar os tais “skeletons”.

Resultado de imagem para skeleton web component
Exemplo de skeleton

Todo este esforço, objetiva mostrar para o usuário, algo da página em 3 segundos, para que o usuário não desista e saia da aplicação.

Track For para obter melhor performance

Quando um array muda, o Angular re-render (redesenha) a arvore DOM, mas, se usar trackBy, Angular irá saber qual elemento mudou e alterará apenas o elemento pertinente.

// in the template<li *ngFor="let item of items; trackBy: trackByFn">{{ item }}</li>// in the componenttrackByFn(index, item) {    
return item.id; // unique id corresponding to the item
}

updateOn

Por padrão sempre que o FormControl muda, o Angular executa o processo de controle de validação. Apartir doAngular 5, temos o updateOn que permite executar as validações no blur ou submit.

Talvez você não sinta a necessidade deste tipo de controle, mas quando começar a validar seu formulário utilizando uma API através de uma validação customizada, vai sentir como esta opção irá lhe ajudar a melhorar a performance.

this.email = new FormControl(null, {
validators: Validators.required,
updateOn: 'blur'
});
this.login = new FormGroup({
email: new FormControl(),
password: new FormControl()
}, { updateOn: 'submit' });

Transpilação

Quando você estuda um pouco sobre o mecanismo de transpilação, vem algumas “manhãs” a mente, que podem ser utilizadas para otimizar a perfomance, como:

1 — Quando tipar os dados de resposta de um requisição, caso seja apenas uma tipagem, sem a necessidade de métodos com comportamento, não crie uma Classe, crie uma Interface. Isto deixa o bundle menor, já que na hora de transpilar, não é gerada uma interface ou qualquer coisa equivalente.

Services no root

Apesar de comodo, você não deveria carregar todos os services na inicialização da aplicação, alguns deles talvez nunca sem usados pelo seu usuário.

Conclusão

Há inúmeras razões para uma aplicação Angular estar lenta, mais do que este texto aborda. Aqui, relato algumas coisas que podem estar afetando a performance, mas lembre-se, estas são baseadas em meu conhecimento, se você souber de outras situações, dicas ou truques que possa utilizar para melhorar a performance, compartilhe, gostaria de aprender com você também.

Assim, como já escrevi no meu texto sobre performance com Java, quero relembrar o leitor de que, ao menos em minha experiência, normalmente a falta de performance está atrelada muito mais ao pouco conhecimento do que a linguagem ou framework utilizada na criação da aplicação.

Você pode utilizar o webpagetest.org para realizar algum tipo de análise de performance da sua aplicação web, de graça.

Que bom que você chegou até o final deste texto, agora eu e você podemos dizer para aquele colega de trabalho parar e mimimi, e pedir para ele também ler este texto e aplicar as otimizações antes de sair falando que Angular é lento ;) Compartilhe assim como eu estou compartilhando!

Leia mais em…

Referências

https://twitter.com/laco2net/status/1213690675385864192

https://docs.google.com/presentation/d/1YbPJtnu34snRYso3SpCsQOq-jBuO84vayvEK27YwGZA/edit#slide=id.g588239c9fa_1_8832

https://github.com/mgechev/optimizing-an-angular-application/tree/onpush
https://www.youtube.com/watch?v=UjXwskqSr9A&lc=
https://medium.freecodecamp.org/best-practices-for-a-clean-and-performant-angular-application-288e7b39eb6f
https://twitter.com/laco2net/status/1213690675385864192

https://github.com/nirkaufman/jspoland-advanced-angular
https://www.youtube.com/watch?v=Q1uORsKjrMo
https://www.youtube.com/watch?v=p9vT0W31ym8
https://www.youtube.com/watch?v=LoIuokh6NUI
https://www.youtube.com/watch?v=yXjYtBdJDyU
https://www.youtube.com/watch?v=FOdetrZCsmU&t=1s
https://www.youtube.com/watch?v=_VnV7R4Ncwc
https://www.youtube.com/watch?v=3K98yzaOww4

--

--