Verhindern Sie das Zwischenspeichern des GET-Aufrufs von IE11 in Angular 2

74

Ich habe einen Ruheendpunkt, der bei einem GET-Aufruf eine Liste zurückgibt. Ich habe auch einen POST-Endpunkt, um neue Elemente hinzuzufügen, und ein LÖSCHEN, um sie zu entfernen. Dies funktioniert in Firefox und Chrome, und POST und DELETE funktionieren in IE11. Das GET in IE11 funktioniert jedoch nur beim ersten Laden der Seite. Beim Aktualisieren werden zwischengespeicherte Daten zurückgegeben. Ich habe einen Beitrag über dieses Verhalten in Angular 1 gesehen, aber nichts für Angular 2 (Release Candidate 1).

cmaynard
quelle
3
Wenn Ihre GET-API keinen Cache Control-Header angibt -> bedeutet dies, dass die Antwort zwischengespeichert werden kann, wenn der Status 200 OK lautet.
Loc
3
Eine clientseitige Problemumgehung finden Sie auch unter stackoverflow.com/questions/36500804/… .
Günter Zöchbauer
@Loc Ich habe die Cache-Control-Werte no-store und no-cache hinzugefügt und erhalte immer noch das gleiche Ergebnis nur im IE.
cmaynard
4
Sieht so aus, als Cache-Control: not-store, no-cachePragma: no-cache Expires: 0
müsste

Antworten:

48

Heute hatte ich auch dieses Problem (verdammter IE). In meinem Projekt benutze ich httpclientdas nicht BaseRequestOptions. Wir sollten es verwenden Http_Interceptor, um es zu lösen!

import { HttpHandler,
    HttpProgressEvent,
    HttpInterceptor,
    HttpSentEvent,
    HttpHeaderResponse,
    HttpUserEvent,
    HttpRequest,
    HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

export class CustomHttpInterceptorService implements HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler):
      Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
      const nextReq = req.clone({
        headers: req.headers.set('Cache-Control', 'no-cache')
          .set('Pragma', 'no-cache')
          .set('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT')
          .set('If-Modified-Since', '0')
      });

      return next.handle(nextReq);
  }
}

Modul zur Verfügung stellen

@NgModule({
    ...
    providers: [
        ...
        { provide: HTTP_INTERCEPTORS, useClass: CustomHttpInterceptorService, multi: true }
    ]
})
Jimmy Ho
quelle
3
Vielen Dank, dies ist die richtige Antwort für Angular 5. Da ich jedoch Angular 2 in der ursprünglichen Frage angegeben habe, lasse ich diese Antwort für diese Frage als richtig markiert.
cmaynard
2
Wenn Sie CORS verwenden, müssen Sie möglicherweise alle Header (Cache-Control, Pragma usw.) Access-Control-Request-Headersauf der Serverseite hinzufügen . Andernfalls treten CORS-Probleme auf. Das heißt, Ihre Anfrage schlägt fehl.
Arikael
57

Für Angular 2 und höher ist es am einfachsten, Header ohne Cache durch Überschreiben hinzuzufügen RequestOptions:

import { Injectable } from '@angular/core';
import { BaseRequestOptions, Headers } from '@angular/http';

@Injectable()
export class CustomRequestOptions extends BaseRequestOptions {
    headers = new Headers({
        'Cache-Control': 'no-cache',
        'Pragma': 'no-cache',
        'Expires': 'Sat, 01 Jan 2000 00:00:00 GMT'
    });
}

Modul:

@NgModule({
    ...
    providers: [
        ...
        { provide: RequestOptions, useClass: CustomRequestOptions }
    ]
})
Vitaliy Ulantikov
quelle
3
Das ist eine großartige Lösung, um alle Anforderungen aus einer Winkelanforderung ohne Cache zu machen. Ich möchte dieses Verhalten jedoch nicht für alle Anforderungen, da einige Anforderungen problemlos zwischengespeichert werden können. Ich habe die entsprechenden Header auf der Serverseite festgelegt. Ich würde es vorziehen, wenn der Server sowieso die Caching-Smarts hat. Ich habe die Frage vielleicht schlecht formuliert.
cmaynard
1
@cmaynard Ich bin auf Ihre Frage gestoßen, als ich mir überlegte, wie man globales Caching für Andular einrichtet. Aus Google-Sicht ist Ihre Formulierung also perfekt für etwas, nach dem die Leute suchen :)
Vitaliy Ulantikov
Diese Lösung funktionierte nicht für mich, ich musste den Cache manuell deaktivieren, indem ich docs.netapp.com/sgws-110/…
Jaikumar H Manjunath
1
Ja, das hat funktioniert, danke. Noch eine Irritation von IE11 !!
Andrew Junior Howard
@cmaynard Ich habe vielleicht darüber nachgedacht und die Option besteht darin, diesen httpinterceptor nur für Ihre Rest-Dienste bereitzustellen.
Joey Gough
4

Bearbeiten : Siehe Kommentar unten - dies ist nicht erforderlich (in den allermeisten Fällen).

Ausgehend von der Antwort von Jimmy Ho oben möchte ich nur das Zwischenspeichern meiner API-Aufrufe verhindern und nicht andere statische Inhalte, die vom Zwischenspeichern profitieren. Alle meine API-Aufrufe beziehen sich auf URLs, die "/ api /" enthalten. Daher habe ich den Code von Jimmy Ho mit einer Überprüfung geändert, bei der die Cache-Header nur hinzugefügt werden, wenn die angeforderte URL "/ api /" enthält:

import { HttpHandler,
    HttpProgressEvent,
    HttpInterceptor,
    HttpSentEvent,
    HttpHeaderResponse,
    HttpUserEvent,
    HttpRequest,
    HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

export class CustomHttpInterceptorService implements HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler):
    Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
    // Only turn off caching for API calls to the server.
    if (req.url.indexOf('/api/') >= 0) {
        const nextReq = req.clone({
            headers: req.headers.set('Cache-Control', 'no-cache')
                .set('Pragma', 'no-cache')
                .set('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT')
                .set('If-Modified-Since', '0')
        });

        return next.handle(nextReq);
    } else {
        // Pass the request through unchanged.
        return next.handle(req);
    }
}

}}

AndrWeisR
quelle
2
Der HttpInterceptor wirkt sich nur auf Anforderungen aus, die über Winkel ausgeführt werden (normalerweise HttpClient in Ihren Winkeldiensten), bei denen es sich in 99% der Fälle um API-Aufrufe handelt. Daher halte ich diese zusätzliche Überprüfung nicht für notwendig. Nur wenn Sie aus irgendeinem Grund statischen Inhalt in Ihrer Service-Schicht anfordern.
LambdaCruiser
@ LambdaCruiser, ja, du hast ganz recht. Es ist nicht wirklich erforderlich. Ich werde das hier lassen, nur für den Fall, dass jemand in die gleiche Falle gerät.
AndrWeisR
1

Wie oben beantwortet, können Sie den http-Anforderungs-Interceptor verwenden, um einen neuen Header für die Anforderung zu ändern oder festzulegen. Im Folgenden finden Sie eine viel einfachere Möglichkeit zum Festlegen von Headern auf dem http-Anforderungs-Interceptor für spätere Winkelversionen ( Winkel 4+ ). Dieser Ansatz würde nur einen bestimmten Anforderungsheader setzen oder aktualisieren. Dies soll verhindern, dass wichtige Header wie der Autorisierungsheader entfernt oder überschrieben werden.

// cache-interceptor.service.ts
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
} from '@angular/common/http';

@Injectable()
export class CacheInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const httpRequest = req.clone({
      headers: req.headers
        .set('Cache-Control', 'no-cache')
        .set('Pragma', 'no-cache')
        .set('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT')
    })

    return next.handle(httpRequest)
  }
}

// app.module.ts

  import { HTTP_INTERCEPTORS } from '@angular/common/http'
  import { CacheInterceptor } from './cache-interceptor.service';

  // on providers
  providers: [{ provide: HTTP_INTERCEPTORS, useClass: CacheInterceptor, multi: true }]
John Wilfred
quelle
0

Deaktivieren Sie das Browser-Caching mit Meta-HTML-Tags: -

<meta http-equiv="cache-control" content="no-cache, must-revalidate, post-check=0, pre-check=0">
<meta http-equiv="expires" content="0">
<meta http-equiv="pragma" content="no-cache">
Mahi
quelle
-1

Ein bisschen spät, aber ich bin auf das gleiche Problem gestoßen. Für Angular 4.X habe ich eine benutzerdefinierte HTTP- Klasse geschrieben, um eine Zufallszahl an das Ende anzuhängen und ein Zwischenspeichern durch den IE zu verhindern. Es basiert auf dem 2. Link von Dimeros ( Was ist httpinterceptor-Äquivalent in angle2? ). Warnung: Nicht garantiert 100% fehlerfrei.

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Http, Response, XHRBackend, RequestOptions, RequestOptionsArgs, 
URLSearchParams } from '@angular/http';

@Injectable()
export class NoCacheHttp extends Http {
    constructor(backend: XHRBackend, options: RequestOptions) {
        super(backend, options);
    }

    get(url: string, options?: RequestOptionsArgs): Observable<Response> {
        //make options object if none.
        if (!options) {
            options = { params: new URLSearchParams() };
        }
        //for each possible params type, append a random number to query to force no browser caching.
        //if string
        if (typeof options.params === 'string') {
            let params = new URLSearchParams(options.params);
            params.set("k", new Date().getTime().toString());
            options.params = params;

        //if URLSearchParams
        } else if (options.params instanceof URLSearchParams) {
            let params = <URLSearchParams>options.params;
            params.set("k", new Date().getTime().toString());

        //if plain object.
        } else {
            let params = options.params;
            params["k"] = new Date().getTime().toString();
        }
        return super.get(url, options);
    }
}
Chris Moore
quelle
1
Ich habe diese Technik in der Vergangenheit verwendet, um das Caching zu "täuschen". Ich denke, es ist nützlich, aber es ist normalerweise besser, stattdessen die entsprechenden Header festzulegen.
cmaynard