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.
Serviç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.
@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');
}
}
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.
Até 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.
@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));
}
}
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.
@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));
}
}
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
Conclusã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!