As maneira mais simples de trafegar dados entre componentes é utilizando inbound property (@Input) e EventEmitter (@Output). Muitas vezes isso é suficiente, porém, podemos passar por alguns problemas:
- E se quiséssemos alterar o dado da variável que estamos passando via inbound property? Teríamos que renderizar o componente novamente?
- E se quiséssemos executar alguma lógica quando detectarmos alteração no valor?
- E se o componente filho alterar esse dado? A única alternativa é criar um "caminho de volta" para avisar o componente pai?
- Será que existe uma forma de manter uma fonte centralizada desse dado caso vários componentes precisem dele?
Uma das alternativas que temos para solucionar esses problemas é fazendo uso do BehaviorSubject ao invés de passar propriedades comuns para outros componentes. Seu funcionamento lembra o EventEmitter, mas trata-se de um Observable que sempre será instanciado e inicializado com um valor. Poderemos passar este BehaviorSubject para outros componentes via inbound property ou centralizá-lo em um serviço responsável pela comunicação, dessa forma, vários componentes poderão se inscrever(subscribe) nessa fonte de dados e manterem-se atualizados do seu último valor.
Resumo
Em um componente pai ou serviço, devemos criar e instanciar um BehaviorSubject que será disponibilizado para outros componentes ou serviços. Novos valores são enviados ao BehaviorSubject através do método next(). Com o subscribe(), podemos detectar quando houve alterações. Podemos obter o valor atual durante o próprio subscribe() e passá-lo a alguma variável ou acessando a propriedade value do BehaviorSubject: nomeBehaviorSubject.value. Também podemos exibir o valor no template html utilizando a pipe async: nomeBehaviorSubject | async.
Na prática
Explicarei sucintamente seu uso e no final disponibilizarei um exemplo funcional, como de praxe. Além do componente principal, terei outros dois componentes, um para emitir valores e outro para exibi-los:
BehaviorSubject via inbound property
O app.component é o nosso "componente pai", que será responsável por criar a instancia do BehaviorSubject que será passada para os componentes filhos:
export class AppComponent {
nomeBehaviorSubjectPai= new BehaviorSubject<string>("Felipe");
}
Teremos então 2 componentes filhos, emissor.component e visualizador.component. Ambos receberão o nomeBehaviorSubjectPai via inbound property.
Definimos um Input para ambos. Não há necessidade de instanciar o BehaviorSubject como feito no app.component:
@Input() nomeBehaviorSubject: BehaviorSubject<string>;
<div class="row">
<div class="col-6 border">
<app-emissor class="m-1" [nomeBehaviorSubject]="nomeBehaviorSubjectPai"></app-emissor>
</div>
<div class="col-6 border">
<app-visualizador class="m-1" [nomeBehaviorSubject]="nomeBehaviorSubjectPai"></app-visualizador>
</div>
</div>
Em emissor.component, mandaremos um novo valor ao nomeBehaviorSubject através do método next(), para isso, criei o método abaixo, que será acionado pelo template:
enviarViaInput(nome: string) {
this.nomeBehaviorSubject.next(nome);
}
Dessa forma já conseguimos acessar o valor diretamente pelo BehaviorSubject recebido pelo nosso visualizador.component. Fiz isso em seu template, conforme código abaixo:
Obs.: não é a única maneira de exibir o valor
<div class="col-12">
Valor: {{nomeBehaviorSubject | async}}
<br>
Recebido {{nomeCount}} vez(es).
</div>
Observe que também estou exibindo o nomeCount. Também podemos nos inscrever ao nomeBehaviorSubject, receber o valor emitido e executar alguma lógica. No caso, apenas estou contando a quantidade de valores que foram recebidos:
ngOnInit() {
this.nomeBehaviorSubjectSubscription = this.nomeBehaviorSubject.subscribe(valor => {
this.nomeCount++;
});
}
BehaviorSubject via service
Seu uso será semelhante ao exemplo anterior, porém com um service criando a instancia do BehaviorSubject, no caso, o app.service, com alguns métodos para passar um novo valor e obter o BehaviorSubject:
export class AppService {
private musicaServiceBehaviorSubject = new BehaviorSubject<string>(`Dio - Egypt`);
constructor() { }
alterarMusica(valor: string) {
this.musicaServiceBehaviorSubject.next(valor);
}
obterMusica() {
return this.musicaServiceBehaviorSubject;
}
}
O emissor.component receberá o service por injeção e poderá passar um novo valor através do método alterarMusica()
constructor(private appService: AppService) { }
enviarViaService(musica: string) {
this.appService.alterarMusica(musica);
}
Da mesma forma, o visualizador.component também recebe o service por injeção e faz uso do subscribe do BehaviorSubject para executar uma lógica:
ngOnInit() {
this.musicaSubscription = this.appService.obterMusica()
.subscribe(valor => {
this.musicaCount++;
});
}
Também criei um método get musica() para acessar o valor do template. O get nos permitirá acessar musica como se fosse uma variável comum de nosso componente, conforme demonstrado:
get musica(): string {
return this.appService.obterMusica().value;
}
<div class="row">
<div class="col-12">
<h4>Via service</h4>
</div>
<div class="col-12">
Valor: {{musica}}
<br>
Recebido {{musicaCount}} vez(es).
</div>
</div>
Vamos ver funcionando?
Recomendo abrir em uma nova aba.
Caso queira abrir em nova aba ou não tenha conseguido visualizar o embedded, clique aqui.
Observe que fiz questão de dar um unsubscribe() nos BehaviorSubjects. Caso não tenha visto meu post anterior, nele demonstro a importância disso:
Observables: cancelar inscrição (unsubscribe) é importante!
Top comments (2)
Olá.
Sempre que dou um next() no serviço, o componente "pisca".
Sabe me dizer como posso resolver?
this._subject.next(_mensagem);
Obrigado.
Poxa, muito bom mesmo.. usei num projeto da empresa aqui, muito obrigado!!