Angular: Automaticamente Cancelar Observables ao Destruir

Nunca mais escreva um ngOnDestroy para limpar suas inscrições

Uma coisa que me irrita muito quando estou trabalhando com Angular é ter que salvar todas as minhas Subscriptions só para me desinscrever delas em ngOnDestroy.

É muito irritante, eu preciso fazer isso em todo lugar. Eu procurei soluções, mas todas elas envolvem declarar ngOnDestroy, e eu não quero fazer isso.

Só quero me inscrever a um Observable e mandar o Angular automaticamente me desinscrever quando o componente for destruído.

Percebi, no entanto, poder conseguir o que quero delegando ngOnDestroy a um serviço, o que me deixou muito animado! Esse problema me incomodava desde que Angular 2 foi lançado.

LinkServiço por componente

Ao fornecer um serviço em um componente, sua existência torna-se dependente do componente.

Quando o componente é destruído, o serviço é destruído também. Vou te mostrar:

Forneça um serviço em seu componente, injete-o no construtor e registre quando ngOnDestroy() acontecer no serviço e no componente.

TypeScript
@Injectable()
export class UnsubscriberService implements OnDestroy {
  public ngOnDestroy(): void {
    console.log('service on destroy');
  }
}

@Component({
  selector: 'app-test',
  template: 'test',
  providers: [UnsubscriberService]
})
export class TestComponent implements OnDestroy {
  constructor(private readonly _unsubscriber: UnsubscriberService) {}

  public ngOnDestroy(): void {
    console.log('component on destroy');
  }
}

Você verá que o serviço é destruído logo antes do componente.

LinkAté que o serviço destrua

Sabendo disso, podemos criar um Observable que emite quando o serviço é destruído e usar takeUntil() para nos desinscrever automaticamente quando isso acontecer.

TypeScript
@Injectable()
export class UnsubscriberService implements OnDestroy {
  private readonly _destroy$ = new Subject<void>();
  public readonly destroy$ = this._destroy$.asObservable();

  public ngOnDestroy(): void {
    this._destroy$.next();
    this._destroy$.complete();
  }
}

@Component({
  selector: 'app-test',
  template: 'test',
  providers: [UnsubscriberService]
})
export class TestComponent implements OnInit {
  constructor(private readonly _unsubscriber: UnsubscriberService) {}

  public ngOnInit(): void {
    timer(0, 500)
      .pipe(takeUntil(this._unsubscriber.destroy$))
      .subscribe((x) => console.log(x));
  }
}

Para simplificar, podemos colocar a lógica do takeUntil() no nosso serviço e a expor através de um método simples.

TypeScript
@Injectable()
export class UnsubscriberService implements OnDestroy {
  private readonly _destroy$ = new Subject<void>();

  public readonly takeUntilDestroy = <T>(
    origin: Observable<T>
  ): Observable<T> => origin.pipe(takeUntil(this._destroy$));

  public ngOnDestroy(): void {
    this._destroy$.next();
    this._destroy$.complete();
  }
}

@Component({
  selector: 'app-test',
  template: 'test',
  providers: [UnsubscriberService]
})
export class TestComponent implements OnInit {
  constructor(private readonly _unsubscriber: UnsubscriberService) {}

  public ngOnInit(): void {
    timer(0, 500)
      .pipe(this._unsubscriber.takeUntilDestroy)
      .subscribe((x) => console.log(x));
  }
}

Eu adicionei isso à minha biblioteca de utilidades para Angular. Se você não quiser digitar nada, basta instalar a biblioteca. npm i @lucaspaganini/angular-utils

LinkConclusão

O que você acha? Gostou? Odiou? Tem uma solução melhor? Deixe sua opinião no meu Twitter. Tenha um ótimo dia e nos vemos em breve!

LinkReferências

  1. Biblioteca angular-utils npm
  2. Repositório angular-utils GitHub

Assine a nossa Newsletter e seja avisado quando eu lançar um curso, postar um vídeo ou escrever um artigo.

Campo obrigatório
Campo obrigatório