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!

Oldest comments (2)

Collapse
 
douglassantanna profile image
Douglas SantAnna Figueredo

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

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.