Comment annuler/abandonner toutes les demandes HTTP en attente angulaires 4+.
Il existe une méthode unsubscribe
pour annuler les demandes HTTP mais comment annuler toutes les demandes en attente en même temps.
Surtout pendant le changement de route.
Il y a une chose que j'ai faite
ngOnDestroy() {
this.subscription.unsubscribe();
}
mais comment y parvenir à l'échelle mondiale
Des idées?
Consultez l'opérateur takeUntil()
de RxJS pour supprimer globalement vos abonnements:
- RxJS 6+ (utilisant la syntaxe pipe
)
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();
}
}
Vous pouvez essentiellement émettre un événement sur votre désinscription Subject
en utilisant next()
chaque fois que vous souhaitez compléter un ensemble de flux. Il est également recommandé de vous désabonner des observables actifs lors de la destruction du composant afin d'éviter les fuites de mémoire.
A lire:
Vous pouvez créer un intercepteur pour appliquer l'opérateur takeUntil
à chaque demande. Ensuite, lors du changement de route, vous émettez une valeur qui annulera toutes les demandes en attente.
@Injectable()
export class HttpCancelInterceptor implements HttpInterceptor {
constructor(private httpCancelService: HttpCancelService) { }
intercept<T>(req: HttpRequest<T>, next: HttpHandler): Observable<HttpEvent<T>> {
return next.handle(req).takeUntil(this.httpCancelService.onCancelPendingRequests())
}
}
Service d'assistance.
@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()
}
}
Accrochez-vous sur les changements de route quelque part dans votre application (par exemple, un composant d'application).
this.router.events.subscribe(event => {
if (event instanceof ActivationEnd) {
this.httpCancelService.cancelPendingRequests()
}
})
Si vous ne souhaitez pas désabonner manuellement tous les abonnements, procédez comme suit:
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)
};
};
}
Ensuite, vous pouvez l'utiliser comme décorateur dans votre composant
@AutoUnsubscribe
export class YourComponent {
}
mais vous devez toujours stocker les abonnements en tant que propriétés de composant . Et lorsque vous naviguez hors composant, la fonction AutoUnsubscribe est activée.
Je ne suis pas convaincu de la nécessité de la fonctionnalité demandée, mais vous pouvez le faire en annulant toutes les demandes en suspens quand et où vous le souhaitez en enveloppant le service http du framework et en lui déléguant.
Cependant, lorsque nous mettons en œuvre ce service, un problème devient rapidement évident. D'une part, nous aimerions éviter de modifier le code existant, y compris le code tiers, qui exploite le client http angular stock. D'autre part, nous aimerions éviter l'héritage d'implémentation.
Pour obtenir le meilleur des deux mondes, nous pouvons mettre en œuvre le service Angular Http
avec notre wrapper. Le code existant continuera à fonctionner sans changement (à condition que ledit code ne fasse rien de stupide comme utiliser http instanceof Http
).
import {Http, Request, RequestOptions, RequestOptionsArgs, Response} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import {Subscription} from 'rxjs/Subscription';
export default interface CancellationAwareHttpClient extends Http { }
export default class CancellationAwareHttpClient {
constructor(private wrapped: Http) {
const delegatedMethods: Array<keyof Http> = [
'get', 'post', 'put', 'delete',
'patch', 'head', 'options'
];
for (const key of delegatedMethods) {
this[key] = wrapped[key].bind(wrapped);
}
}
cancelOutstandingRequests() {
this.subscriptions.forEach(subscription => {
subscription.unsubscribe();
});
this.subscriptions = [];
}
request(url: string | Request, options?: RequestOptionsArgs) {
const subscription = this.wrapped.request(url, options);
this.subscriptions.Push(subscription);
return subscription;
}
subscriptions: Subscription[] = [];
}
Notez que les déclarations interface
et class
pour CancellationAwareHttpClient
sont fusionnées. De cette manière, notre classe implémente Http
en vertu de la clause interface
de la déclaration extends
.
Maintenant, nous allons fournir notre service
import {NgModule} from '@angular/core';
import {ConnectionBackend, RequestOptions} from '@angular/http';
import CancellationAwareHttpClient from 'app/services/cancellation-aware-http-client';
let cancellationAwareClient: CancellationAwareHttpClient;
const httpProvider = {
provide: Http,
deps: [ConnectionBackend, RequestOptions],
useFactory: function (backend: ConnectionBackend, defaultOptions: RequestOptions) {
if (!cancellationAwareClient) {
const wrapped = new Http(backend, defaultOptions);
cancellationAwareClient = new CancellationAwareHttpClient(wrappedHttp);
}
return cancellationAwareClient;
}
};
@NgModule({
providers: [
// provide our service as `Http`, replacing the stock provider
httpProvider,
// provide the same instance of our service as `CancellationAwareHttpClient`
// for those wanting access to `cancelOutstandingRequests`
{...httpProvider, provide: CancellationAwareHttpClient}
]
}) export class SomeModule {}
Notez comment nous substituons le service fourni par la structure existante. Nous utilisons une usine pour créer notre instance et n’ajoutons pas de décorateurs pour DI à l’emballeur lui-même afin d’éviter un cycle dans l’injecteur.
ngOnDestroy
callback est généralement utilisé pour tout nettoyage personnalisé devant se produire lorsque l'instance est détruite.
où voulez-vous annuler votre demande?
peut-être que si vous voulez annuler vos demandes sur le navigateur fermé, il y a une idée créative ici
Essaye ça :
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Rx';
export class Component implements OnInit, OnDestroy {
private subscription: Subscription;
ngOnInit() {
this.subscription = this.route.params.subscribe();
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Vous pouvez créer un service HTTP personnalisé (à l'aide de HttpClient) qui gère une liste des demandes en attente. Chaque fois que vous envoyez un service http à ce service personnalisé au lieu de Http/HttpClient, placez maintenant les abonnements dans une liste et, au retour de la réponse, supprimez-les. En utilisant cela, vous aurez tous les abonnements incomplets dans une liste.
Maintenant, dans le même service personnalisé, injectez le routeur dans le constructeur et abonnez-vous dessus pour obtenir les événements de changement de route. Maintenant, chaque fois que cette observable émet, tout ce que vous avez à faire est de vous désabonner de tous les abonnements présents dans la liste et d’en extraire tous les éléments.
Si vous avez besoin d'un extrait de code, mentionnez-le en commentaire.