DEV Community

Cover image for BehaviorSubject para comunicação entre componentes
Felipe Carvalho
Felipe Carvalho

Posted on

BehaviorSubject para comunicação entre componentes

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");
}
Enter fullscreen mode Exit fullscreen mode

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>;
Enter fullscreen mode Exit fullscreen mode
<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>
Enter fullscreen mode Exit fullscreen mode

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);
  }
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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++;
    });
  }
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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++;
    });
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode
<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>
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
brunogz profile image
BrunoGZ

Olá.
Sempre que dou um next() no serviço, o componente "pisca".
Sabe me dizer como posso resolver?

this._subject.next(_mensagem);

Obrigado.

Collapse
 
douglassantanna profile image
Douglas SantAnna Figueredo

Poxa, muito bom mesmo.. usei num projeto da empresa aqui, muito obrigado!!