Skip to content

Instantly share code, notes, and snippets.

@jamesmfriedman
Created August 16, 2016 13:54
Show Gist options
  • Save jamesmfriedman/c769fbabd79e34bf46cb0c744757b6e7 to your computer and use it in GitHub Desktop.
Save jamesmfriedman/c769fbabd79e34bf46cb0c744757b6e7 to your computer and use it in GitHub Desktop.
var mod = angular.module('CacheView', []);
function addScope(child, parent){
child.$parent = parent;
child.$$prevSibling = parent.$$childTail;
if(child.$$prevSibling) child.$$prevSibling.$$nextSibling = child;
parent.$$childTail = child;
if(!parent.$$childHead) parent.$$childHead = child;
// works, but screws up the watcher count for some reason
parent.$$watchersCount += child.$$watchersCount;
child.__proto__ = parent;
return child;
}
function removeScope(scope){
if (scope.$$prevSibling){
scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
scope.$$prevSibling = null;
} else if (scope.$parent) {
scope.$parent.$$childHead = null;
}
if (scope.$$nextSibling){
scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
scope.$$nextSibling = null;
} else if (scope.$parent) {
scope.$parent.$$childTail = null;
}
if (scope.$parent) {
if(scope.$parent.$$childHead && !scope.$parent.$$childTail) scope.$parent.$$childTail = scope.$parent.$$childHead;
if(!scope.$parent.$$childHead && scope.$parent.$$childTail) scope.$parent.$$childHead = scope.$parent.$$childTail;
}
scope.$parent = null;
return scope;
}
mod.directive('edesiaCacheView', function ($compile, cacheView, $stateParams) {
return {
restrict: 'E',
priority: 10000,
transclude: true,
compile: function(element, attrs, transcludeFn){
var childScope;
var cacheName = attrs.name;
var cached = cacheView.get(cacheName);
return function(scope, element, attrs) {
var valid = true;
var stateParamsCopy;
// If we have a cache, we need to check on whether or not to invalide it
// base on the cacheInvalidateOn array in routes.
if (cached && attrs.cacheInvalidateOn) {
var keys = attrs.cacheInvalidateOn.split(',');
var results = keys.filter(function(key){
// check our new state params against the cached version
return cached.params[key] === $stateParams[key];
});
// if our params match, we are still valid
valid = results.length === keys.length;
}
scope.$on('$stateChangeStart', function(){
stateParamsCopy = _.assign({}, $stateParams);
});
// cache mode
if (cached && valid){
childScope = cached.scope;
element.append(cached.element);
// wait till the animation is done to bring our scope back
var unbind = scope.$on('$viewContentAnimationEnded', function(){
addScope(childScope, scope);
childScope.$broadcast('$outCache', childScope);
scope.$evalAsync();
unbind();
});
}
// no cache
else {
childScope = scope.$new();
transcludeFn(childScope, function(clone){
element.append(clone);
});
}
// This is bound to the original DOM node being destroyed
// in this case, we know the parent is always the ui-view node.
// If we wanted to use this generically
// we would have to select UI-VIEW or NG-VIEW
element.parent().on('$destroy', function(){
cacheView.put(cacheName, {
element: element.children().detach(),
scope: childScope,
params: stateParamsCopy
});
});
// grab the scope as soon as the state changes
// this helps performance since digest cycles get called multiple times
// while the views are animating.
setTimeout(function() {
scope.$on('$stateChangeSuccess', function(evt, toState, toParams, fromState){
var isChildState = fromState.name.split('.').shift() === toState.name.split('.').shift();
if (!isChildState) {
removeScope(childScope);
childScope.$broadcast('$inCache', childScope);
}
});
});
};
}
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment