{ Angular LifeCycle Hooks } Controle sua Aplicação como um Mestre Jedi

Angular

{ Angular LifeCycle Hooks } Controle sua Aplicação como um Mestre Jedi

Gabriel Nascimento
Escrito por Gabriel Nascimento em 12 de julho de 2020

O Angular LifeCycle Hooks é um poderoso recurso presente em 99% dos projetos Angulares…

Afinal ele ajuda você a obter um controle bastante refinado da sua aplicação, além de:

  • Ser fácil de usar;
  • Manter seu código limpo;
  • E aplicar boas práticas ao seu projeto.

Portanto uma ótima ferramenta para acrescentar ao seu Arsenal de Programador

Então continue lendo esse artigo para descobrir o que é e como tirar o melhor proveito do Angular LifeCycle:

O que é Angular LifeCycle Hooks?

Primeiramente o que é o Life Cycle (ou ciclo de vida)?

Cada componente em sua aplicação Angular possui um ciclo de vida… Ou seja, várias fases que vão desde a criação até sua destruição.

Além disso você pode entrar no ciclo de vida do componente para realizar tarefas em pontos específicos durante sua execução.

Para isso o Angular fornece os Hooks (ou ganchos), que são métodos especiais adicionados ao componente e executados durante cada fase do Angular LifeCycle.

Sendo que os ganchos disponíveis são executados na seguinte ordem:

ngOnChanges: chamado uma vez na criação do componente e sempre que houver alteração em uma de suas propriedades de entrada. Ou seja, mudanças no Input() decorator e no property binding.

ngOnInit: chamado uma única vez quando o componente é inicializado (logo após o primeiro ngOnChanges).

ngDoCheck: chamado a cada ciclo de detecção de alterações (processo que percorre o componente atrás de mudanças). Portanto use ao invés do ngOnChanges para alterações que o Angular não detecta.

ngOnDestroy: chamado antes do Angular destruir o componente.

Além disso existem outros 4 ganchos dentro do ngDoCheck:

ngAfterContentInit: chamado depois que o conteúdo externo é inserido no componente (por exemplo, vindo do ng-content).

ngAfterContentChecked: invocado após a verificação do conteúdo externo.

ngAfterViewInit: chamado logo após o conteúdo do próprio componente e de seus filhos ser inicializado.

ngAfterViewChecked: chamado cada vez que o conteúdo do componente é verificado pelo mecanismo de detecção de alterações do Angular.

Caso você queira ter um Guia Rápido de Referências do Angular LifeCycle Hooks, veja esse infográfico personalizado.

Como funciona o Angular LifeCycle Hooks?

Robô BB-8 de Star Wars

Em resumo para utilizar os ganchos do ciclo de vida, você deve:

  • Implementar a interface do respectivo gancho;
  • E criar o método especial com seu nome.

Por exemplo, veja como ficaria o uso do ngOnInit():

import { Component, OnInit } from '@angular/core';

@Component({...})
export class AppComponent implements OnInit {

  constructor() { }

  ngOnInit() {
    console.log('Olá, componente Iniciado!');
  }

}

Note que o nome da interface OnInit é o mesmo do método, porém sem o prefixo ng

E todos os outros ganchos seguem a mesma lógica, veja por exemplo o ngOnChanges():

import { Component, OnChanges } from '@angular/core';

@Component({...})
export class AppComponent implements OnChanges {

  constructor() { }

  ngOnChanges() {
    console.log('Valores de entrada alterados...');
  }

}

Alias a implementação da interface não é obrigatória e o método irá funcionar mesmo sem ela.

Porém é uma boa prática você deixar explícito para outros desenvolvedores quais ganchos estão em uso no componente.

E se você deseja utilizar vários ganchos, é igualmente fácil:

import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({...})
export class AppComponent implements OnInit, OnDestroy {

  constructor() { }

  ngOnInit() {
    console.log('Olá, componente Iniciado!');
  }

  ngOnDestroy() {
    console.log('Adeus, componente destruído!');
  }
}

Aliás para você ver todo esse processo na prática, vou deixar um ótimo vídeo da Loiane Groner sobre o assunto:

Com isso você já consegue aplicar o Angular LifeCycle Hooks em seus projetos e esse Guia Rápido de Referências pode ser de grande ajuda no começo…

Porém caso você queira se aprofundar nas boas práticas e exemplos de uso mais comuns… então continue lendo esse artigo até o final.

OnChanges

ngOnChanges do Angular LifeCycle Hooks

É o primeiro gancho do ciclo de vida e executado sempre que houver alterações nas propriedades de entrada

Como por exemplo, os dados externos vindos do @Input() ou passados ao property binding.

Mas porque o OnInit não é o primeiro? Mesmo sendo responsável pela inicialização…

Antes de inicializar, o componente precisa ter todos os valores internos bem definidos, ou seja:

  • Suas propriedades de classe, criadas pelo constructor;
  • Suas dependências injetadas, também pelo constructor;
  • E suas propriedades de entrada, vindas de Input;

E você lembra quem é responsável por monitorar essas propriedades de entrada?

Exato, o OnChanges! Então faz sentido ele executar antes do OnInit.

Assim o Angular garante a você acesso a qualquer informação do componente dentro do OnInit.

Quando usar o OnChanges

Você percebeu que tudo gira em torno dos valores de @Input() e property binding?

Então caso você execute alguma lógica toda vez que um desses valores é alterado, o OnChanges é a arma certa para usar.

Por exemplo, realizar uma soma sempre que o @Input() value sofrer mudança:

import { Component, OnChanges, Input } from '@angular/core';

@Component({...})
export class SumComponent implements OnChanges {

  @Input() value: number;
  result: number;

  ngOnChanges() {
    this.result = value + 2;
    // Qualquer outra lógica que você queria realizar...
  }

}

O objeto SimpleChanges

O método ngOnChanges() também recebe como parâmetro opcional um objeto SimpleChanges que contém cada valor de @Input() definido no componente.

Para isso cada propriedade em SimpleChanges possui um objeto filho SimpleChange:

interface SimpleChanges {
  [propName: string]: SimpleChange;
}

Que por sua vez possui os seguintes valores:

interface SimpleChange {
  currentValue: any;
  previousValue: any;
  firstChange: boolean;
  isFirstChange(): boolean;
}
  • currentValue: contém o valor atual do Input;
  • firstChange: informa true se for a primeira alteração realizada e false nas modificações seguintes;
  • previousValue: contém o valor de Input antes da alteração ocorrer. Sendo útil ao comparar valores atuais e anteriores;
  • isFirstChange(): método auxiliar que retorna true se for a primeira alteração no valor.

Note que o objeto SimplesChange fornece vários recursos muito úteis…

Assim ele permite que você inspecione as mudanças ocorridas e tome decisões inteligentes com base nos valores do objeto.

Por exemplo, verificar se o valor da variável contact é diferente do anterior:

@Input() contact: number;

ngOnChanges(changes: SimpleChanges) {
  if (changes.contact.currentValue !== changes.contact.previousValue) {
    console.log('O valor do contato é diferente do anterior!');
  }
}

OnInit

ngOnInit do Angular LifeCycle Hooks

É uma maneira poderosa de adicionar lógica de inicialização, ou seja…

Se você precisa executar muitas tarefas quando o componente é criado, então uma boa prática fazer dentro do gancho OnInit:

import { Component, OnInit } from '@angular/core';

@Component({...})
export class AppComponent implements OnInit {

  constructor() { }

  ngOnInit() {
    this.setupData();
    this.doStuff();
  }

  setupData() {...}

  doStuff() {...}

}

Mas porque não utilizar o constructor?

Vimos que, antes de inicializar, o componente precisa ter todos os seus valores internos bem definidos. Sendo essa a maneira do Angular garantir a você acesso a qualquer informação necessária dentro do OnInit.

Caso contrario você pode enfrentar inconsistências. Como as propriedades @Input() estarem disponíveis no OnInit, mas serem indefinidas no constructor:

import { Component, OnInit, Input } from '@angular/core';

@Component({...})
export class UserComponent implements OnInit {

  @Input() user: any;

  constructor() {
    console.log(this.user); // undefined
  }

  ngOnInit() {
    console.log(this.user); // { User Object }
  }

}

Observables e ngOnInit

O casos de uso mais comuns do OnInit, é atribuir Observables a variáveis do componente:

user$: Observable<any>;

constructor(private userService: UserService) { }

ngOnInit() {
  this.user$ = this.userService.getUser();
}

Sobretudo essa é uma boa prática para os Observables serem inicializados em um tempo previsível no ciclo de vida.

Além de fornecer um lugar comum para os desenvolvedores encontrarem seus Observables.

E o mesmo vale para assinaturas manuais em Observables:

user: any;

constructor(private userService: UserService) { }

ngOnInit() {
  this.userService.getUser().subscribe(
    (userResult: any) => this.user = userResult;
  ); 
}

Parâmetros de rota no OnInit

A maioria dos aplicativos Angular utiliza roteamento e às vezes você precisa recuperar parâmetros dessa rota.

Para isso o angular disponibiliza o ActivatedRoute.params… onde você pode assinar e retirar os valores da rota.

E assim como os outros Observables, esse também é manipulado dentro do OnInit:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({...})
export class SimpleComponent implements OnInit {

  id: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.params.subscribe(
      (params: any) => this.id = params['id'];
    );
  }

}

Perceba que atribuir Observables no método ngOnInit(), faz parte das boas maneiras de código.

Formulários reativos e ngOnInit

Por fim ao usar formulários reativos no Angular, precisamos construir um objeto FormGroup através do serviço FormBuilder.

Porém muitas vezes os campos do formulário dependem de valores vindos de propriedades @Input()

E caso você realize essa operação dentro do constructor, os valores Input vão ser indefinidos e resultará em erros:

import { Component, Input } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({...})
export class MyFormComponent {
  
  @Input() defaultDescription: string;
  form: FormGroup;
  
  constructor(private formBuilder: FormBuilder) {
    this.form = this.formBuilder.group({
      description: [this.defaultDescription] // Valor: Undefined
    });
  }

}

Então por segurança, devemos construir o FormGroup no interior do ngOnInit():

import { Component, OnInit, Input } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({...})
export class MyFormComponent implements OnInit {

  @Input() defaultDescription: string;
  form: FormGroup;

  constructor(private formbuilder: FormBuilder) { }

  ngOnInit() {
    this.form = this.formBuilder.group({
      description: [this.defaultDescription] // Valor normal
    });
  }

}

Lembre-se, na dúvida, é melhor evitar o uso do constructor e priorizar o OnInit.

OnDestroy

ngOnDestroy do Angular LifeCycle Hooks

Por último o OnDestroy executa tarefas antes do componente ser destruído… o que torna ele um recurso poderoso para adicionar lógicas de limpeza ao final do ciclo de vida.

Como você pode ver, a implementação do OnDestroy é bastante direta…

Mas o que seria uma lógica de limpeza?

Para isso abordaremos 2 casos de uso muito comuns:

Cancelar assinatura em Observable para evitar vazamentos de memória

Primeiramente o que são vazamentos de memória?

Em resumo é uma falha no programa em liberar memória que não está mais em uso. Causando problemas de desempenho e até mesmo erros no projeto.

Um clássico exemplo de vazamento no Angular é quando você assina um Observable:

import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';
import { Subscription } from 'rxjs';

@Component({...})
export class UserComponent implements OnInit {

  userSub: Subscription; // Armazena assinatura do Observable

  constructor(private userService: UserService) { }

  ngOnInit() {
    this.userSub = this.userService.getUser().subscribe(
      (user: any) => console.log(`Usuário logado: ${user}`);
    ); 
  }

}

Mas qual é o problema com o código acima?

Acontece que quando um componente é destruído, suas assinaturas em Observables não são eliminadas junto.

E caso esse componente seja criado e destruído 3 vezes, você terá 3 assinaturas inúteis ocupando espaço e gastando seu processamento…

Então essas assinaturas se acumulam como um bola de neve… até causar lentidão em sua aplicação.

E para resolver esse problema, você precisa cancelar a assinatura antes do componente ser destruído. Para isso use os métodos unsubscribe() e ngOnDestroy():

import { Component, OnInit, OnDestroy } from '@angular/core';
import { UserService } from './user.service';
import { Subscription } from 'rxjs';

@Component({...})
export class UserComponent implements OnInit, OnDestroy {

  userSub: Subscription; // Armazena assinatura do Observable

  constructor(private userService: UserService) { }

  ngOnInit() {
    this.userSub = this.userService.getUser().subscribe(
      (user: any) => console.log(`Usuário logado: ${user}`);
    );
  }

  ngOnDestroy() {
    if (this.userSub) {
      this.userSub.unsubscribe();
    }
  }

}

Assim você impedi que as assinaturas se acumulem…

Note que verificamos se this.userSub existe antes de chamar o método unsubscribe(). Isso evita erro no caso da assinatura não ter sido criada. 

Realizar o logout do usuário

Além de limpar vazamentos na Aplicação, podemos usar o OnDestroy para realizar chamadas de limpeza no servidor

Por exemplo, caso você precise executar o logout do usuário assim que o componente for destruído:

ngOnDestroy() {
  if (this.userSub) {
    this.userSub.unsubscribe();
  }
  this.authService.logout();
}

Agora quando o UserComponent parar de ser utilizado, será feita uma chamada para desconectar o usuário no servidor.

Lembre-se que esses são apenas os 2 exemplos mais comuns, mas pense no OnDestroy sempre que precisar “limpar seus rastros”.

Por ter chegado até aqui, parabéns você merece!

ilustração do mestre yoda

Afinal esse não foi um artigo fácil…

Aprender a usar o Angular LifeCycle Hooks com eficiência requer conhecimento de vários recursos Angulares.

Porque para controlar sua Aplicação como um verdadeiro mestre Jedi, você primeiro precisa saber o que está controlando.

Por fim lembre-se dos pontos mais importantes de cada gancho…

OnChanges:

  • Cuida de monitorar alterações nas propriedades de entrada (@Input() e property binding);
  • Use quando a lógica envolver esses valores de entrada;
  • Executa antes do OnInit.

OnInit:

  • Cuida da inicialização do componente;
  • De preferência ao OnInit e não ao constructor;
  • Sempre assine Observables e crie formulários reativos no OnInit.

DoCheck:

  • É chamado a cada ciclo de detecção de alterações;
  • Use para monitorar alterações que o Angular não detecta, ou seja, que OnChanges não abrange.

OnDestroy:

  • É chamado antes do componente ser destruído;
  • Use para cancelar suas assinaturas de Observables e realizar o logout de seus usuários.

E caso você 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 *

9 Replies to “{ Angular LifeCycle Hooks } Controle sua Aplicação como um Mestre Jedi”

Henning

Se garante muito no angular Gabriel, parabéns!!!

Gabriel Nascimento

Obrigado, Henning!!!

julio

Sen-sa-cio-nal!

Gabriel Nascimento

O-bri-ga-dão!

sylvio

Amigo, parabéns pelo trabalho! Ficou muito fácil de entender com sua explicação. Obrigado!

Gabriel Nascimento

Valeu, Sylvio!

diego

Muito bom o artigo, e de excelente didática! Parabéns e muito obrigado!

Gabriel Nascimento

Eu que agradeço, fico feliz que você gostou. xD