Sunday, June 9, 2013

Angular.js and IE8 caching

Older Internet Explorer versions are notorious for agressively caching AJAX requests. In this post, you'll find two techniques that combat this behaviour.

The first option is to have your server explicitly set the caching headers.
Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
Response.Cache.SetValidUntilExpires(false);
Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetNoStore();
Since you don't necessarily own the server, or clients might already have cached some requests, you can trick the browser into thinking each request is a fresh one by making each url unique. Our old pal jQuery already learned this trick years ago. Angular.js on the other hand seems to have forgotten. We can get around though.

If you merge this pull request (or wait for angular.js version 1.2), you will find angular's HTTP provider augmented with request interceptors, enabling you to mold the request before it goes out.

The interceptor we're adding to kill the cache only touches GET requests, appending a 'cacheSlayer' querystring parameter with a timestamp to each url, making it unique and thus bypassing the cache. A factory is responsible for creating it, while a config block pushes it into a collection of interceptors.
var AppInfrastructure = angular.module('App.Infrastructure', []);

AppInfrastructure
    .config(function ($httpProvider) {
        $httpProvider.requestInterceptors.push('httpRequestInterceptorCacheBuster');
    })    
    .factory('httpRequestInterceptorCacheBuster', function () {
        return function (promise) {
            return promise.then(function (request) {
                if (request.method === 'GET') {
                    var sep = request.url.indexOf('?') === -1 ? '?' : '&';
                    request.url = request.url + sep + 'cacheSlayer=' + new Date().getTime();
                }

                return request;
            });
        };
    });    
I hope this helps someone spending time on more important matters. 

8 comments:

  1. $httpProvider.requestInterceptors is undefined, what Angular version you used. In my version of 1.0.7, I only get $httpProvider.responseInterceptors.

    ReplyDelete
    Replies
    1. Have you merged the pull request as described above?

      Delete
  2. instead of merging. just use built in interceptors:

    $httpProvider.interceptors.push('cacheBuster');

    myApp.factory('cacheBuster', function($q) { //thanks IE. thanks a BUNCH!!!! (* ̄m ̄)
    return {
    'request' : function(request) {
    if(request.method === 'GET') {
    var sep = request.url.indexOf('?') === -1 ? '?' : '&';
    request.url = request.url + sep + 'cacheBuster=' + new Date().getTime();
    }
    return request || $q.when(request);
    }};
    });

    ReplyDelete
  3. Not sure when the interceptor syntax changed but at least in the 1.2.x branch the code should look more like this:

    myapp.config(['$httpProvider', function ($httpProvider) {
    $httpProvider.interceptors.push(function() {
    return {
    request: function(request) {
    if (request.method === 'GET') {
    var sep = request.url.indexOf('?') === -1 ? '?' : '&';
    request.url = request.url + sep + 'cacheBust=' + new Date().getTime();
    }
    return request;
    }
    };
    });
    }])

    ReplyDelete