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?
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
É 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 doInput
;firstChange:
informatrue
se for a primeira alteração realizada efalse
nas modificações seguintes;previousValue:
contém o valor deInput
antes da alteração ocorrer. Sendo útil ao comparar valores atuais e anteriores;isFirstChange():
método auxiliar que retornatrue
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
É 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
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!
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()
eproperty 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 aoconstructor
; - 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.
Obrigado por compartilhar uma explicação tão esclarecedora, até mesmo para quem está no início da jornada Angular.
Tamo junto! Boa sorte em sua jornada e qualquer dúvida fique a vontade para entrar em contato. ;D
Brilhante Gabriel, sua explicação foi a é melhor, pela primeira vez entendi os demais ciclos, nem com a documentação e os vídeo da Loiane Groner tinha ficado tão claro como esse seu artigo, parabéns!
VOCÊ ACABOU DE SALVAR A MINHA VIDA DE DEV JÚNIOR COM ESSE ARTIGO. Muito obrigada!!! <3
Tá suuuuuuper salvo, favoritado e vou consultar ele demais na minha trajetória com Angular!
Fico feliz que o artigo foi de alguma ajuda! Boa sorte para você em sua trajetória!
E se tiver algum problema, fique a vontade para entrar em contato. ;D
Se garante muito no angular Gabriel, parabéns!!!
Obrigado, Henning!!!
Sen-sa-cio-nal!
O-bri-ga-dão!
Amigo, parabéns pelo trabalho! Ficou muito fácil de entender com sua explicação. Obrigado!
Valeu, Sylvio!
Muito bom o artigo, e de excelente didática! Parabéns e muito obrigado!
Eu que agradeço, fico feliz que você gostou. xD
Referências:
Angular DOC
Codecraft
It Next
Pusher
Ultimate Courses
Ultimate Courses
Loiane Groner Youtube