60
votes

I currently use service/$resource to make ajax calls (GET in this case), and IE caches the calls so that fresh data cannot be retrieved from the server. I have used a technique I found by googling to create a random number and append it to the request, so that IE will not go to cache for the data.

Is there a better way than adding the cacheKill to every request?

factory code

.factory('UserDeviceService', function ($resource) {

        return $resource('/users/:dest', {}, {
            query: {method: 'GET', params: {dest: "getDevicesByUserID"}, isArray: true }
        });

Call from the controller

$scope.getUserDevices = function () {
        UserDeviceService.query({cacheKill: new Date().getTime()},function (data) {
            //logic
        });
    }
10
I have added No-Cache headers on the server side and it worked for now.Ufuk Hacıoğulları
Ufuk, that seems to be the most effective way to do take care of the problem "globally" or in a more scalable way, without having to make every ajax/REST call unique. You should answer the question, so I can mark it as the answer! You deserve the credit.binarygiant
BTW, I am using node/express for the web server, and I accomplished what Ufuk recommended by adding the following code to express config: app.use(function noCache(req, res, next){ res.header("Cache-Control", "no-cache, no-store, must-revalidate"); res.header("Pragma", "no-cache"); res.header("Expires",0); next(); })binarygiant

10 Answers

36
votes

As binarygiant requested I am posting my comment as an answer. I have solved this problem by adding No-Cache headers to the response on server side. Note that you have to do this for GET requests only, other requests seems to work fine.

binarygiant posted how you can do this on node/express. You can do it in ASP.NET MVC like this:

[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
public ActionResult Get()
{
    // return your response
}
50
votes

As described in one of my other posts, you could disable caching globally in the $httpProvider:

myModule.config(['$httpProvider', function($httpProvider) {
    //initialize get if not there
    if (!$httpProvider.defaults.headers.get) {
        $httpProvider.defaults.headers.get = {};    
    }    

    // Answer edited to include suggestions from comments
    // because previous version of code introduced browser-related errors

    //disable IE ajax request caching
    $httpProvider.defaults.headers.get['If-Modified-Since'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
    // extra
    $httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
    $httpProvider.defaults.headers.get['Pragma'] = 'no-cache';
}]);
33
votes

For those using ASP.NET Web API 2 the equivalent solution would be this (Web API does not use same caching logic as MVC):

public class NoCacheHeaderFilter : ActionFilterAttribute
{
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        if (actionExecutedContext.Response != null) // can be null when exception happens
        {
            actionExecutedContext.Response.Headers.CacheControl =
                new CacheControlHeaderValue { NoCache = true, NoStore = true, MustRevalidate = true };
            actionExecutedContext.Response.Headers.Pragma.Add(new NameValueHeaderValue("no-cache"));

            if (actionExecutedContext.Response.Content != null) // can be null (for example HTTP 400)
            {
                actionExecutedContext.Response.Content.Headers.Expires = DateTimeOffset.UtcNow;
            }
         }
    }
}

then attach it in WebApiConfig.cs:

public static void Register(HttpConfiguration config)
{
    ....
    config.Filters.Add(new NoCacheHeaderFilter());

    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );
}
33
votes

Enabling noCache in the is instance was the best way to accomplish this:

In node/express this works to prevent IE from caching those requests:

app.use(function noCache(req, res, next) {
    res.header("Cache-Control", "no-cache, no-store, must-revalidate");
    res.header("Pragma", "no-cache");
    res.header("Expires", 0);
    next();
});
12
votes

you may add an interceptor to generate unique request url. Also you may remove console.log calls

myModule.config(['$httpProvider', function($httpProvider) {
 $httpProvider.interceptors.push('noCacheInterceptor');
}]).factory('noCacheInterceptor', function () {
            return {
                request: function (config) {
                    console.log(config.method);
                    console.log(config.url);
                    if(config.method=='GET'){
                        var separator = config.url.indexOf('?') === -1 ? '?' : '&';
                        config.url = config.url+separator+'noCache=' + new Date().getTime();
                    }
                    console.log(config.method);
                    console.log(config.url);
                    return config;
               }
           };
    });
4
votes

I get it resolved by:

$http.get("/your_url?rnd="+new Date().getTime()).success(function(data, status, headers, config) {
    console.log('your get response is new!!!');
});
4
votes

Koajs equivalent of binarygiant's answer:

app.use(route.get('*', noCache));

function* noCache(path, next){
    this.set('cache-control', 'no-cache, no-store, must-revalidate');
    this.set('pragma',  'no-cache');
    this.set('expires', 0);
    yield next;
}
3
votes

While this approach:

myModule.config(['$httpProvider', function($httpProvider) {
    //initialize get if not there
    if (!$httpProvider.defaults.headers.get) {
        $httpProvider.defaults.headers.get = {};    
    }
    //disable IE ajax request caching
    $httpProvider.defaults.headers.get['If-Modified-Since'] = '0';
}]);

Is correct, '0' is not a valid value for the If-Modified-Since header. It needs to be a valid HTTP-date, for example:

If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT

According to the spec:

A recipient MUST ignore the If-Modified-Since header field if the
received field-value is not a valid HTTP-date, or if the request
method is neither GET nor HEAD.

So better be safe than sorry and use an actual date in the past.

If you have any control over the server output, it would be preferable to add no caching headers to that instead.

2
votes

My solution was adding Cache-Control: no-cache header on the server, plus adding $templateCache.remove() before changing state. I'm using angular-ui/ui-router. I was having issue with IE11 and Edge browser.

$templateCache.remove('/partials/details.html');
$state.go('details');
-3
votes

An obvious solution is to use unique urls. But how can the router urls be changed post initialization Disabling browser caches is not an option, since we need this for normal operations. You could remove templates from the $templateCache when those are no longer needed. (http://docs.angularjs.org/api/ng.$templateCache). Those new ones are added to the cache as soon as downloading completes.