Skip to main content

RXJS | Angular

  • RXJS | Angular

Operadores do Rxjs

  • Artigo explicando

Basic RxJS Operators and How to Use Them - This Dot Labs

  • Operardor PIPE

    • Este é o método do RxJS para aplicar operadores ao fluxo de um Observable antes de você se inscrever nele. Ele canalizará o valor emitido do Observable através de cada operador passado como argumento, antes de passar o valor final transformado para o subscribemétodo.
  • Operador OF

    • O operador of cria um observable que emite valores em sequência, seguido de uma notificação de conclusão

    Uma notificação de conclusão informa aos assinantes do Observable que o Observable não estará mais emitindo novos valores.

    const arr = [1, 2, 3];

    const arr$ = of(arr);

    arr$.subscribe((values) => console.log(`Emitted Values: `, values));

    //Valores emitidos: [1, 2, 3]
  • Operador FROM

    • O operador from transforma um Array, Promise ou Iterable em um Observable.

    Ao contrário ofde , ele emitirá cada elemento em um Array ou Iterable em sequência, em vez do valor completo. Uma vez que todos os elementos do Array ou Iterable tenham sido emitidos, uma notificação de conclusão é enviada para qualquer assinante.

    const arr = [1, 2, 3];

    const arr$ = from(arr);

    arr$.subscribe((values) => console.log(`Emitted Values: `, values));

    //Valores emitidos: 1
    //Valores emitidos: 2
    //Valores emitidos: 3

    Podemos usar uma string para emitir

    const fromString$ = from("Hello");
    fromString$.subscribe((value) => console.log(`Emitted Values: `, value));

    //Valores emitidos: H
    //Valores emitidos: e
    //Valores emitidos: l
    //Valores emitidos: l
    //Valores emitidos: o

  • Operador MAP

    • O operador map é um operador de transformação. Ele pega valores de um Observável, os transforma e cria um novo Observável que emite os valores transformados.
  • Subject

    • são observáveis que nos permite gerenciar valores por conta própria
    const subject = new Subject<string>();

    subject.next('Adolfo');
    //após chamar o método next, todos que tenham se inscrito nesse subject receberam o valor que passarmos

    subject.subscribe((value) => {console.log(value)})

    Understanding RxJS with Angular

    • O operador debounceTime é um dos mais utilizados no #RxJS. Seu exemplo clássico é o do Typeahead, onde ignoramos valores digitados em um período de tempo para evitar consultas desnecessárias no servidor.
  • Operador TAKE

    • O valor que vai no take é um número que especifica quantos valores o Observable deve emitir antes de completar. Em outras palavras, ele limita o número de emissões que o Observable pode fazer.

      import { of } from 'rxjs';
      import { take } from 'rxjs/operators';

      const observable = of(1, 2, 3);
      observable.pipe(take(1)).subscribe((x) => {
      console.log(x);
      });

Como evitar vazamento de memória no Angular com takeUntil?

pipe(take(1)) garante que você obtenha um "single Value observable" significando que ele emitirá um valor e então completará e fechará a assinatura, portanto, sem vazamento de memória. Se seu Service ou função por si só emite apenas um Single value, pipe(take(1)) não faz nada.

Por exemplo, se você tem uma chamada REST, com httpclient.get(...) ela pode haver um atraso. Então você deve usar pipe(timeout(3000)) ou pipe(takeUntil(...)) para ter certeza de não ter uma assinatura ativa, se seu componente ou o que for destruído, você não terá um vazamento de memória ou comportamento inesperado da lógica dentro da assinatura. Mesmo com pipe(take(1)) um vazamento de memória pode existir porque ele só será concluído após exatamente um valor ou erro ser emitido.

Então, se você tem um http.get() e ele tem um atraso de rede. E você usaria "pipe(take(1))" ele ainda causaria um vazamento de memória porque ele espera por exatamente um valor, e a assinatura será disparada quando o valor chegar, mesmo se o Componente no qual você fez a chamada for destruído ou se você tiver navegado para outra View do seu aplicativo.

takeUntil(...) é útil para componentes, se eles forem destruídos você pode acionar o término de uma assinatura em ngDestroy().

private destroy$ = new Subject<void>();

// Assinatura com takeUntil
this.httpClient.get('sua-api-endpoint')
.pipe(takeUntil(this.destroy$))
.subscribe(response => {
// Lógica de tratamento da resposta
});

// No método ngOnDestroy
ngOnDestroy() {
// Emite um valor para sinalizar o término das assinaturas
this.destroy$.next();
}
Details

Explicação sobre Os métodos .next() e .complete() são utilizados em Subjects do RxJS para controlar o fluxo de dados e o ciclo de vida das emissões. Vamos detalhar o que cada um faz e como eles se aplicam no contexto do exemplo com takeUntil.

O que é um Subject?

Um Subject é um tipo especial de Observable que permite tanto emitir valores quanto se inscrever para receber valores. Ele atua como uma ponte entre a emissão de valores e os assinantes.

Método .next(value)

  • Função: Emite um novo valor para todos os assinantes do Subject.
  • No contexto do takeUntil: Quando você utiliza takeUntil(this.destroy$), o Observable ao qual você está se inscrevendo continuará emitindo valores até que o this.destroy$ emita um valor. Ao chamar this.destroy$.next(), você está sinalizando para todos os Observables dependentes que é hora de se completarem.

Exemplo:

// Dentro do componente
private destroy$ = new Subject<void>();

// Assinatura com takeUntil
this.httpClient.get('sua-api-endpoint')
.pipe(takeUntil(this.destroy$))
.subscribe(response => {
// Lógica de tratamento da resposta
});

// No método ngOnDestroy
ngOnDestroy() {
// Emite um valor para sinalizar o término das assinaturas
this.destroy$.next();
}

Método .complete()

  • Função: Notifica todos os assinantes de que não serão emitidos mais valores e completa o Subject.
  • Por que usar: Completar o Subject é uma boa prática para liberar recursos e evitar possíveis vazamentos de memória associados ao próprio Subject.

Exemplo Continuado:

ngOnDestroy() {
this.destroy$.next(); // Emite o valor para o takeUntil
this.destroy$.complete(); // Completa o Subject
}

Resumo da Operação:

  1. this.destroy$.next();

    • Emite um valor para o Subject.
    • Todos os Observables que estão utilizando takeUntil(this.destroy$) recebem esse valor e se completam automaticamente.
    • Isso garante que nenhuma assinatura permaneça ativa após o componente ser destruído.
  2. this.destroy$.complete();

    • Completa o Subject em si.
    • Libera recursos associados ao Subject.
    • É uma boa prática para evitar vazamentos de memória relacionados ao Subject.

Por que isso é importante?

  • Gerenciamento de Ciclo de Vida: Em aplicações Angular, componentes podem ser criados e destruídos dinamicamente. Garantir que todas as assinaturas sejam encerradas ao destruir um componente é crucial para manter a aplicação eficiente.
  • Prevenção de Vazamentos de Memória: Assinaturas ativas em componentes destruídos podem continuar consumindo memória e recursos, levando a vazamentos de memória.
  • Fluxo de Dados Consistente: Completar assinaturas evita que lógicas assíncronas sejam executadas fora do contexto esperado, o que poderia causar comportamentos inesperados ou erros.

Boa Prática:

Sempre que utilizar takeUntil com um Subject para gerenciar assinaturas, certifique-se de:

  • Emitir um valor com .next() para sinalizar o momento de completar as assinaturas.
  • Completar o Subject com .complete() para liberar recursos associados.

Exemplo Completo:

import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Component, OnDestroy } from '@angular/core';

@Component({
// ... metadados do componente
})
export class MeuComponente implements OnDestroy {
private destroy$ = new Subject<void>();

ngOnInit() {
this.httpClient.get('sua-api-endpoint')
.pipe(takeUntil(this.destroy$))
.subscribe(response => {
// Lógica de tratamento da resposta
});
}

ngOnDestroy() {
this.destroy$.next(); // Emite o valor para o takeUntil
this.destroy$.complete(); // Completa o Subject
}
}

Conclusão

Os métodos .next() e .complete() são fundamentais para controlar o fluxo de dados e o ciclo de vida das assinaturas em Observables. Usá-los corretamente garante que sua aplicação Angular seja eficiente e livre de vazamentos de memória.

Se tiver mais dúvidas ou precisar de mais detalhes, fique à vontade para perguntar!

Explicação sobre take e takeUntil

A diferença entre os operadores take(1) e takeUntil no contexto do RxJS está no comportamento e nos casos de uso de cada um:


1. take(1)

  • O que faz: O operador take(1) emite apenas o primeiro valor do Observable e, em seguida, completa automaticamente.
  • Quando usar:
    • Quando você só precisa do primeiro valor emitido pelo Observable e pode ignorar todos os outros valores subsequentes.
    • É útil para Observables que emitem vários valores, mas você está interessado apenas no primeiro (por exemplo, requisições HTTP que emitem uma única resposta).

Exemplo:

this.http.get('/api/data').pipe(
take(1)
).subscribe(value => console.log(value));
  • Neste caso, assim que o primeiro valor é emitido, a assinatura se completa e não há necessidade de cancelá-la manualmente.
  • Não depende de nenhum evento externo para encerrar.

2. takeUntil

  • O que faz: O operador takeUntil mantém a assinatura ativa até que um segundo Observable emita um valor ou complete, momento em que o Observable principal também será completado.
  • Quando usar:
    • Quando você precisa controlar dinamicamente o ciclo de vida de uma assinatura, geralmente vinculando-o a um evento externo (como a destruição de um componente ou o encerramento de um processo).
    • É útil em situações onde você quer encerrar um Observable assim que algo acontece.

Exemplo:

import { Subject } from 'rxjs';

private destroy$ = new Subject<void>();

ngOnInit() {
this.http.get('/api/data').pipe(
takeUntil(this.destroy$)
).subscribe(value => console.log(value));
}

ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
  • Aqui, o takeUntil depende de um Observable (this.destroy$) que será acionado no método ngOnDestroy, completando a assinatura associada.
  • Ideal para gerenciar o ciclo de vida em componentes ou serviços.

Comparação Prática

Aspectotake(1)takeUntil
Completa automaticamente?Sim, após o primeiro valor.Não, depende de outro Observable.
Usa um Observable externo?Não.Sim, para determinar quando encerrar.
Quando usar?Para pegar apenas o primeiro valor emitido.Para gerenciar assinaturas dinamicamente.
ComplexidadeSimples e direto.Requer um Observable extra para controle.

Exemplo Prático Comparativo

Imagine que você está lidando com eventos de rolagem em um componente, mas quer parar de escutar esses eventos quando o componente for destruído.

  • Com take(1) (não aplicável neste caso):

    • Isso não funciona, pois você só receberia o primeiro evento e perderia os demais.
  • Com takeUntil:

    import { fromEvent, Subject } from 'rxjs';
    import { takeUntil } from 'rxjs/operators';

    private destroy$ = new Subject<void>();

    ngOnInit() {
    fromEvent(window, 'scroll').pipe(
    takeUntil(this.destroy$)
    ).subscribe(() => console.log('User scrolled!'));
    }

    ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
    }
    • Aqui, o takeUntil permite que você continue ouvindo os eventos de rolagem até que o componente seja destruído, momento em que o Observable será completado.

Resumo:

CenárioEscolha
Preciso apenas do primeiro valor.take(1)
Quero controlar a assinatura dinamicamente (e.g., até o componente ser destruído).takeUntil

Complete, Next e Error

warning

Quando é lançado um erro o Complete não é chamado, apenas quando o Next é chamado

next: (v) => {
try {
const apiResponse = ApiResponse.fromJson(v);
if (apiResponse.success) {
this.userSubject.next({ username, isLoggedIn: true });
console.log('Login bem-sucedido');
} else {
console.error(apiResponse.message || 'Erro desconhecido');
}
} catch (error) {
console.error(
'Erro ao processar a resposta da API:',
(error as Error).message
);
}
},
error: (e: Error) => {
this.loadingSubject.next(false);
console.error(`Houve um erro ao realizar a operação: Mensagem ${e.message}`)
},
complete: () => {
this.loadingSubject.next(false);

console.info('complete');
},