Utilizando TrackByFunction com ngFor no framework Angular.

Boa prática ao utilizar o ngFor em sua aplicação!

Utilizando TrackByFunction com ngFor no framework Angular.

A diretiva ngFor é muito conhecida por desenvolvedores que trabalham com Angular. O comportamento dessa diretiva é percorrer um array (uma lista) que contêm objetos, exibindo cada item do array como elemento no HTML. Esse comportamento se assemelha bastante a um loop de repetição das linguagens de programação que já conhecemos. O que vamos aprender neste artigo é como aplicar uma técnica chamada TrackByFunction que permite identificar todos os elementos que o ngFor vai percorrer.

Por que precisamos do trackByFunction?

Antes de apresentar a técnica, precisamos entender sobre o comportamento do Framework ao renderizar uma lista de objetos no HTML. Existe um custo de processamento ao realizar essa tarefa. Em aplicações de pequeno porte isso talvez nem seja um problema, principalmente se sua DOM renderiza poucos elementos e seu array contém poucos objetos. Para isso vamos ilustrar com uma situação:

Seu cliente precisa que exiba uma tabela contendo uma lista de produtos que foram cadastradas em seu sistema de gerenciamento. Toda vez que ele aperta o botão de refresh é feita uma requisição para o Back-end (para os serviços) para verificar se existe ou não uma lista atualizada com um novo produto. No inicio a lista vem com poucos novos produtos atualizados, afinal a empresa tem apenas meses de vida. Mas ao passar dos meses novos usuários começam a se cadastrar na plataforma e postar novos produtos e essa lista de 200 produtos, salta para 2200 produtos cadastrados. Com essa nova situação é preciso uma nova abordagem para exibir os produtos no front-end. Por que? Toda vez que seu usuário clicar no botão refresh, o comportamento padrão do Angular é remover todos os elementos DOM associados aos dados e os criará novamente no HTML, mesmo se dados iguais estiverem chegando. E isso tem um custo de processamento. Mas você deve estar pensando que a paginação já resolveria esse problema e adicionando uma ordenação trazendo sempre o ultimo produto adicionado nessa lista em primeiro lugar. Mas é bom lembrar que uma página pode ter 30 ou mais elementos renderizados tudo depende da necessidade do cliente. Pode ser que no futuro ele queira que essa tabela tenha uma funcionalidade, exibir mais, adicionado elementos novos aquela página da tabela!

De qualquer maneira o problema aqui é o desempenho que sua aplicação pode apresentar em situações em que precisa renderizar vários elementos sendo que alguns não precisariam ser renderizados novamente. Se a resposta do back-end é uma nova lista com 2000 produtos sendo que apenas 3 são novos, não seria muito inteligente destruir todos os elementos e criar novamente. O ngFor não sabe dizer ao certo ao percorrer na lista qual propriedade é única e que não precisa ser removida e que apenas basta adicionar um novo objeto. Se ele não sabe identificar os elementos que devem ficar, simplesmente destrói todos e os exibe novamente. Vamos ver isso na prática!

Primeiro passo é entender o comportamento padrão do framework, como ele não consegue identificar que apenas precisa adicionar um elemento a DOM, o comportamento que verá no GIF exemplifica bem como todos os elementos dentro do ngFor serão destruídos e renderizados novamente:

20211220_145601.gif

Notou o comportamento? Todos os elementos HTML que ficaram destacados quando cliquei em refresh foram removidos (destruídos) e o Framework teve que adicionar tudo novamente com a inclusão da nova propriedade que surgiu no array.

Para solucionar isso temos o recurso trackBy, que permite rastrear elementos quando eles são adicionados ou removidos do array. Para isso precisamos retornar um identificador exclusivo para cada objeto no array. No caso desse exemplo isso se aplica muito bem ao Id que cada objeto contém.

Podemos criar um método para isso no componente responsável por aquele HTML:

import { Component } from '@angular/core';
interface Item {
  id: number;
  name: string;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})

export class AppComponent {
  title = 'trackby-example';
  items: Item[] = [
    { id: 1, name: 'HTML' },
    { id: 2, name: 'CSS' },
    { id: 3, name: 'JavaScript' },
  ];

  addItem() {
    this.items = [
      { id: 1, name: 'HTML' },
      { id: 2, name: 'CSS' },
      { id: 3, name: 'JavaScript' },
      {
        id: 4,
        name: 'Angular',
      },
    ];
  }

// Método que permite o ngFor identificar quais elementos devem ser adicionados ou removidos
  trackBy(index: number, item: Item) {
    return item.id;
  }
}

É importante destacar que trackBy recebe dois parâmetros. O primeiro é o índice do item, ou seja, qual posição do array cada objeto se encontra. O segundo é nosso item (que contém as propriedades desse objeto) como parâmetros. E como fica a DOM? Podemos agora adicionar a chamada dessa função no ngFor:

// app.component.html

<div>
  <ul>
    <li *ngFor="let item of items; trackBy: trackBy">{{ item.name }}</li>
  </ul>
</div>

<input type="button" value="Add Angular" (click)="addItem()" />

Preste atenção no que acontece no HTML quando eu clico no botão de Add Angular:

20211220_152104.gif

O comportamento da DOM foi bem diferente agora! Já que o método trackBy retorna a propriedade Id que é exclusiva para cada objeto, ao usar essa função, o Angular saberá quais itens da lista precisam ser renderizados novamente, sem ter que destruir todo o DOM e reconstruí-lo.

Mas e se na resposta do back-end, dentro da lista um objeto teve uma propriedade atualizada? Em nosso exemplo vamos alterar o objeto com id 3 que estará como React para JavaScript, adicionei também uma outra propriedade chamada status para cada objeto, note o comportamento que vamos ter:

20211220_182751.gif

Apenas dois objetos desse array foram renderizados novamente. Então fica claro que o Framework agora entende que existem alterações naquelas propriedades específicas e que elas precisam ser alteradas. Tivemos duas ações nesse exemplo uma atualização de um objeto e adição de um novo na DOM. Os outros nem precisaram ser renderizados! Isso nos ajuda muito ao melhorar e aprimorar o front-end!

Conclusão

O recurso trackBy traz grandes benefícios para aplicações que trabalham muito com alterações constantes na DOM e utilizam muito a diretiva do ngFor. Basta analisar sempre quando deve usar este recurso.

Fico por aqui neste post, agradeço por ler. Se tiver alguma dúvida ou crítica construtiva deixe nos comentários. Muito obrigado e até o próximo post. 😉

Referência:

Documentação Angular: angular.io/api/core/TrackByFunction