Skip to content

Instantly share code, notes, and snippets.

@r3m0t
Created February 28, 2014 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save r3m0t/9271790 to your computer and use it in GitHub Desktop.
Save r3m0t/9271790 to your computer and use it in GitHub Desktop.
Angular digest_limit example
<!doctype html>
<html>
<!--
a far simpler alternative to pasvaz.bindonce.
Use the once directive to remove all watchers from the children and replace
them with one.
-->
<head>
<script src="/lib/jquery/jquery.js"></script>
<script src="/lib/angular/angular.js"></script>
<style type="text/css">
.unique-id { border:3px solid red; }
.yellow { border:3px solid #DAA520; }
.green { border:3px solid green; }
.blue { border:3px solid blue; }
</style>
<script>
angular.module('myApp', [])
.directive('veryUniqueId', function () {
var id = 1;
return {
restrict: 'E',
replace: true,
template: '<span class="unique-id" title="unique id"></span>',
link: function (scope, iElement, iAttrs) {
iElement.text(id);
id++;
}
};
})
.directive('reDigestCount', function () {
return {
restrict: 'E',
replace: true,
template: '<span class="green" title="watches run since this element was created"></span>',
link: function (scope, iElement, iAttrs) {
var id = 0;
scope.$watch(function () {
id++;
iElement.text(id);
});
}
};
})
.directive('simpleOnce', function () {
return {
restrict: 'A',
scope: true,
link: function (scope, iElement, iAttrs) {
scope.$$postDigest(scope.$destroy.bind(scope));
}
};
})
.directive('once', ['$animate', function($animate) {
// very much like
// https://github.com/angular/angular.js/blob/v1.2.13/src/ng/directive/ngIf.js
return {
transclude: 'element',
priority: 599, // less than ngIf
terminal: true,
restrict: 'A',
$$tlb: true,
link: function ($scope, $element, $attr, ctrl, $transclude) {
var block, childScope;
$scope.$watch($attr.once, function onceWatchAction(value) {
// delete old
if (block) {
$animate.leave(getBlockElements(block.clone));
block = null;
}
// add new!
if (!childScope || childScope.$$destroyed) {
childScope = $scope.$new();
$transclude(childScope, function (clone) {
// Note: We only need the first/last node of the cloned nodes.
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
// by a directive with templateUrl when it's template arrives.
block = {
clone: clone
};
$animate.enter(clone, $element.parent(), $element);
childScope.$$postDigest(childScope.$destroy.bind(childScope));
});
}
}, !!$attr.onceEq);
}
};
function getBlockElements(nodes) {
var startNode = nodes[0],
endNode = nodes[nodes.length - 1];
if (startNode === endNode) {
return angular.element(startNode);
}
var element = startNode;
var elements = [element];
do {
element = element.nextSibling;
if (!element) break;
elements.push(element);
} while (element !== endNode);
return angular.element(elements);
}
}])
.directive('newOnce', [function () {
return {
scope: true,
link: function (scope, element, attrs) {
scope.$digestLimit(attrs.newOnce, !!attrs.newOnceEq);
}
}
}])
.controller('TodoCtrl', function TodoCtrl($scope, $compile, $sce, $timeout, $templateCache) {
$scope.todos = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}];
$scope.addTodo = function() {
$scope.todos.push({text:$scope.todoText, done:false});
$scope.todoText = '';
};
$scope.remaining = function() {
var count = 0;
angular.forEach($scope.todos, function(todo) {
count += todo.done ? 0 : 1;
});
return count;
};
$scope.archive = function() {
var oldTodos = $scope.todos;
$scope.todos = [];
angular.forEach(oldTodos, function(todo) {
if (!todo.done) $scope.todos.push(todo);
});
};
var FakeScope = function () {};
FakeScope.prototype.$watch = function (watchExpression, listener, objectEquality) {
console.log("So you want to watch", watchExpression, listener, objectEquality);
}
var origTemplate = angular.element($templateCache.get('origTemplate'));
var compiler = $compile(origTemplate);
/*
var fakeScope1 = new FakeScope();
fakeScope1.a = 2;
var x = compiler(fakeScope1);
var fakeScope2 = new FakeScope();
fakeScope2.a = "JJ";
var y = compiler(fakeScope2);
*/
var realScope = $scope.$new(true);
realScope.a = "jj";
compiler(realScope, function (newElem, myScope) {
$("#k").replaceWith(newElem);
myScope.$$postDigest(myScope.$destroy.bind(myScope));
});
var realScope2 = $scope.$new(true);
realScope2.a = "kk";
compiler(realScope2, function (newElem, myScope) {
$("#k2").replaceWith(newElem);
myScope.$$postDigest(myScope.$destroy.bind(myScope));
});
$scope.$root.$watch(function () {
console.log("re-digest");
return 0;
}, function () {});
//$timeout(function () { realScope.a = "NEW!"; }, 500);
var items_made = 3;
$scope.new_item = function () {
console.log("new item");
var newObj = {c: items_made * 100};
newObj.t = newObj.c;
items_made++;
$scope.items.push(newObj);
}
$scope.change_item = function () {
console.log("change item");
$scope.items[0].c += 1;
$scope.items[0].t += 1;
}
$scope.anew = function () {
$scope.items = [{c: 0, t: 0}, {c: 100, t: 100}, {c: 200, t:200}];
}
$scope.move = function () {
var a = $scope.items.pop();
var b = $scope.items.pop();
$scope.items.push(a);
$scope.items.push(b);
}
$scope.anew();
});
</script>
</head>
<body ng-app="myApp">
<script type="text/ng-template" id="origTemplate">
<div>
<span class="yellow">a={{a}}</span>
<very-unique-id></very-unique-id>
</div>
</script>
<div ng-controller="TodoCtrl">
<!-- simple-once creates a child scope for the child elements,
which it destroys after the next stable $digest.
It doesn't support computing the insides more than once.
It's slow. -->
<ul style="float:right">
<li>simple-once</li>
<li ng-repeat="item in items track by item.c">
<very-unique-id></very-unique-id>
<re-digest-count></re-digest-count>
{{item.c}}
<span simple-once class="yellow">
<very-unique-id></very-unique-id>
<re-digest-count></re-digest-count>
{{item.t}}
</span>
</li>
</ul>
<!-- a form that shows the actual state of the items array -->
<ul>
<li ng-repeat="item in items">
<label>c: <input ng-model="item.c"></label>
<label>t: <input ng-model="item.t"></label>
<label>s: <input ng-model="item.s"></label>
</li>
<li><button ng-click="new_item()">New item</button></li>
</ul>
<!-- once is like simple-once but it recreates the child scope
whenever the attribute's value (here item.s) changes.
This is slow because we do all the linking inside every time.
-->
<ul style="float:right;clear:right">
<li>once</li>
<li ng-repeat="item in items track by item.c">
<very-unique-id></very-unique-id>
<re-digest-count></re-digest-count>
{{item.c}}
<span once="item.s" class="yellow">
<very-unique-id></very-unique-id>
<re-digest-count></re-digest-count>
{{item.t}}
</span>
</li>
</ul>
<!-- new-once uses $digestLimit in my angular fork to keep
its child scope in the tree but have it skipped when nothing's changed.
It's fast (probably) but may have surprising behaviour when nested?
Let's find out.
-->
<ul style="float:right;clear:right">
<li>new-once</li>
<li ng-repeat="item in items track by item.c">
<very-unique-id></very-unique-id>
<re-digest-count></re-digest-count>
{{item.c}}
<span new-once="[item.s]" new-once-eq=1 class="yellow">
<very-unique-id></very-unique-id>
<re-digest-count></re-digest-count>
{{item.t}}
</span>
</li>
</ul>
<button ng-click="move()">Move items</button>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment