61

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
        });
    }
Ufuk Hacıoğulları
  • 37,978
  • 12
  • 114
  • 156
binarygiant
  • 6,362
  • 10
  • 50
  • 73
  • 2
    I have added No-Cache headers on the server side and it worked for now. – Ufuk Hacıoğulları Jun 06 '13 at 20:50
  • 1
    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 Jun 07 '13 at 20:02
  • 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 Jun 07 '13 at 20:08

10 Answers10

50

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';
}]);
Community
  • 1
  • 1
cnmuc
  • 6,025
  • 2
  • 24
  • 29
  • 1
    JSHint might complain about `$httpProvider.defaults.headers.get['Pragma'] = 'no-cache';` so it's safe to change that line to `$httpProvider.defaults.headers.get.Pragma = 'no-cache';` – yvesmancera Aug 21 '15 at 22:12
  • Can I don't use those extras? – xlhuang Aug 26 '15 at 10:56
  • 1
    For those of you who are using Angular Resource you have to substitute $httpProvider with $resourceProvider – James Feb 10 '17 at 22:12
36

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
}
Ufuk Hacıoğulları
  • 37,978
  • 12
  • 114
  • 156
33

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();
});
Samuel Bolduc
  • 18,163
  • 7
  • 34
  • 55
binarygiant
  • 6,362
  • 10
  • 50
  • 73
33

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 }
    );
}
UserControl
  • 14,766
  • 20
  • 100
  • 187
  • 4
    This solved our issue. We also had to add `if(actionExecutedContext.Request.Method.Method == "GET")` in the `OnActionExecuted` event to make it only fire on GET requests – RichardC Dec 12 '14 at 11:48
  • 3
    You'll have to compare to the HttpMethod and not just the string. Like this: `actionExecutedContext.Request.Method == HttpMethod.Get` – Tony Karel Jan 15 '15 at 20:36
  • I had been using the method suggested by @cnmuc, but it would interfere with some External rest services I use (some services rejected the request stating that those headers were not accepted). Applying your filter worked like a charm. – aplon Feb 17 '17 at 15:07
11

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;
               }
           };
    });
dillip
  • 1,782
  • 17
  • 16
  • 1
    A nicer solution instead of manipulate the url would be: check if the request has parameters `config.params = config.params || []` and then add your default noCache parameter `config.params['noCache'] = new Date().getTime();` – pasine Mar 04 '15 at 11:29
4

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!!!');
});
khichar.anil
  • 4,585
  • 1
  • 23
  • 21
4

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;
}
Felix
  • 3,783
  • 5
  • 34
  • 53
3

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.

Community
  • 1
  • 1
Brother Woodrow
  • 6,092
  • 3
  • 18
  • 20
2

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');
Ganesh Yadav
  • 2,607
  • 2
  • 29
  • 52
Hexadecy
  • 44
  • 3
-3

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.