Skip to content

Instantly share code, notes, and snippets.

@cmoore4
Created June 10, 2016 16:25
Show Gist options
  • Save cmoore4/75192bf59396ad888195489c1ca26da1 to your computer and use it in GitHub Desktop.
Save cmoore4/75192bf59396ad888195489c1ca26da1 to your computer and use it in GitHub Desktop.
A method, using Angular's promise notify functionality, to immediately restore previous page contents on renavigating to the same page, while simultaneously getting new data and updating the cache.
// These two resources helped me out:
// http://www.webdeveasy.com/interceptors-in-angularjs-and-useful-examples/
// https://code.angularjs.org/1.4.0/docs/api/ng/service/$q
module.factory('httpCacher', ['localStorageService', '$q', '$timeout', '$injector', function(localStorageService, $q, $timeout, $injector) {
var cachePrefix = 'ng_http_cache_';
var cacheExpire = 1000 * 60 * 60 * 8; // 8 Hour Expirey
// http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
// This will be the key, along with cachePrefix above for our localstorage key:value pair,
// hashing the request object
String.prototype.hashCode = function() {
var hash = 0;
if (this.length === 0) return hash;
for (var i = 0; i < this.length; i++) {
var char = this.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
};
var cache = {
get: function(key) {
var val = localStorageService.get(key);
if (val) {
return val;
} else {
return {};
}
},
set: function(key, value, config) {
// Clear existing cache expirations for this key
var expires = window.expires || {};
if (expires[key]) {
clearTimeout(expires[key]);
}
// Set a new cache expiration for the key
var expire = setTimeout(function() {
localStorageService.remove(key);
}, cacheExpire);
expires[key] = expire;
return localStorageService.set(
key, {
data: value,
time: new Date().getTime(),
request: config
}
);
}
};
var cacheInjector = {
request: function(request) {
// Only get cache idempotent actions
if (['GET', 'HEAD', 'OPTIONS'].indexOf(request.method) === -1) {
return request;
}
var key = cachePrefix + JSON.stringify(request).hashCode();
var val = cache.get(key);
if (val.data) {
var $http = $injector.get('$http'),
defer = $q.defer();
defer.notify(val);
$timeout(function() {
defer.notify(val);
defer.resolve(request);
}, 10, false);
return defer.promise;
}
return request;
},
response: function(response) {
if (['GET', 'HEAD', 'OPTIONS'].indexOf(response.config.method) === -1) {
return response;
}
var key = cachePrefix + JSON.stringify(response.config).hashCode();
var val = response.data;
cache.set(key, val, response.config);
return response;
}
};
return cacheInjector;
}]);
module.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('httpCacher');
}]);
// Using this file as example:
// https://bitbucket.org/cgdev/acrew-app/src/22ceeae5c12dc2ca61d2cb3f5ef5f38d7289867c/src/views/company/company.js?at=master&fileviewer=file-view-default
'use strict';
angular.module('Acrew.company', ['ngRoute', 'lbServices'])
.controller('companyController', ['$scope', '$rootScope', '$location', 'Company', 'Job', 'localStorageService', '$routeParams',
function($scope, $rootScope, $location, Company, Job, localStorageService, $routeParams) {
$scope.company = {};
$scope.jobs = [];
$rootScope.pageTitle = 'Company';
$rootScope.isLoading = true;
// This will be called by both cache hits and server responses
$scope.setCompany = function(company) {
$scope.company = company;
localStorageService.set('company', company);
};
Company.findOne({
filter: {
where: {
id: $routeParams.id
}
}
}).$promise.then(
// Success callback
function(data) {
console.log('[company#Company.find] Success');
$scope.setCompany(data);
},
// Eror callback
function(err) {
console.error('[company#Company.find] Error', err);
},
// The third promise callback argument executes everytime $q.notify is called.
// We send back the cached value on notify as the request is sent to the server.
function(cache) {
$scope.setCompany(cache.data);
// We turn off the main loading indicator, and load a smaller "updating" spinner in the corner
$rootScope.isLoading = false;
$rootScope.backgroundLoading = true;
// Finally *always* executes after success or failure, and here we make sure we always toggle off the
// loading indicators
}).finally(function() {
$rootScope.isLoading = false;
$rootScope.backgroundLoading = false;
});
// ... cut remaining code for demo purposes
}
]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment