How to cancel/unsubscribe all pending HTTP requests in Angular 4+
Checkout the takeUntil()
operator from RxJS to globally drop your subscriptions :
- RxJS 6+ (using the pipe
syntax)
import { takeUntil } from 'rxjs/operators';
export class YourComponent {
protected ngUnsubscribe: Subject<void> = new Subject<void>();
[...]
public httpGet(): void {
this.http.get()
.pipe( takeUntil(this.ngUnsubscribe) )
.subscribe( (data) => { ... });
}
public ngOnDestroy(): void {
// This aborts all HTTP requests.
this.ngUnsubscribe.next();
// This completes the subject properlly.
this.ngUnsubscribe.complete();
}
}
- RxJS < 6
import 'rxjs/add/operator/takeUntil'
export class YourComponent {
protected ngUnsubscribe: Subject<void> = new Subject<void>();
[...]
public httpGet(): void {
this.http.get()
.takeUntil(this.ngUnsubscribe)
.subscribe( (data) => { ... })
}
public ngOnDestroy(): void {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
You can basically emit an event on your unsubscribe Subject
using next()
everytime you want to complete a bunch of streams. It is also good practice to unsubscribe to active Observables as the component is destroyed, to avoid memory leaks.
Worth reading :
Avoiding take until leaks
A great answer from seangwright
You can create an interceptor to apply takeUntil
operator to every request. Then on route change you will emit event that will cancel all pending requests.
@Injectable()
export class HttpCancelInterceptor implements HttpInterceptor {
constructor(private httpCancelService: HttpCancelService) { }
intercept<T>(req: HttpRequest<T>, next: HttpHandler): Observable<HttpEvent<T>> {
return next.handle(req).pipe(takeUntil(this.httpCancelService.onCancelPendingRequests()))
}
}
Helper service.
@Injectable()
export class HttpCancelService {
private cancelPendingRequests$ = new Subject<void>()
constructor() { }
/** Cancels all pending Http requests. */
public cancelPendingRequests() {
this.cancelPendingRequests$.next()
}
public onCancelPendingRequests() {
return this.cancelPendingRequests$.asObservable()
}
}
Hook on route changes somewhere in your app (e.g. onInit in appComponent).
this.router.events.subscribe(event => {
if (event instanceof ActivationEnd) {
this.httpCancelService.cancelPendingRequests()
}
})
And last but not least, register the interceptor to your app.module.ts:
import { HttpCancelInterceptor } from 'path/to/http-cancel.interceptor';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
@NgModule({
[...]
providers: [
{
multi: true,
provide: HTTP_INTERCEPTORS,
useClass: HttpCancelInterceptor
}
],
[...]
})
export class AppModule { }
If you don't want to manually unsubscribe all subscriptions, then you can do this:
export function AutoUnsubscribe(constructor) {
const original = constructor.prototype.ngOnDestroy;
constructor.prototype.ngOnDestroy = function() {
for (const prop in this) {
if (prop) {
const property = this[prop];
if (property && (typeof property.unsubscribe === 'function')) {
property.unsubscribe();
}
}
}
if (original && typeof original === 'function') {
original.apply(this, arguments)
};
};
}
Then you can use it as decorator in your component
@AutoUnsubscribe
export class YourComponent {
}
but you still need to store subscriptions as component properties. And when you navigating out of component, AutoUnsubscribe function will occurs.