Skip to content

Commit 5fb6509

Browse files
committed
Let angular use ObjectObserve for method observation
1 parent 98443d6 commit 5fb6509

File tree

3 files changed

+149
-17
lines changed

3 files changed

+149
-17
lines changed

src/ng/directive/ngRepeat.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,14 @@ var ngRepeatDirective = ngDirective({
8888
// We need an array of these objects since the same object can be returned from the iterator.
8989
// We expect this to be a rare case.
9090
var lastOrder = new HashQueueMap();
91-
scope.$watch(function(scope){
91+
// Change the way we watch arrays. Instead of iterating on each digest, we now iterate
92+
// on change in array. We don't yet support watching array contents, because angular does
93+
// not have such concept, but it should be straight forward to add. This means that the
94+
// demo app needs to create new array instance, but can reuse array items.
95+
scope.$watch(rhs, function(newValue, oldValue, scope){
9296
var index, length,
93-
collection = scope.$eval(rhs),
97+
// no need to compute the collection, since it is passed in
98+
collection = newValue,
9499
collectionLength = size(collection, true),
95100
childScope,
96101
// Same as lastOrder but it has the current state. It will become the

src/ng/rootScope.js

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,15 @@ function $RootScopeProvider(){
136136
this['this'] = this.$root = this;
137137
this.$$asyncQueue = [];
138138
this.$$listeners = {};
139+
140+
var scope = this;
141+
// Global observer for objects
142+
this.$$observer = new ChangeSummaryDispatcher();
143+
this.$$observer.observePathValue(this.$$asyncQueue, 'length', function() {
144+
// Whenever we add $evalAsync, then automatically trigger 4digest
145+
// to flush it in the current micro-task.
146+
scope.$digest();
147+
});
139148
}
140149

141150
/**
@@ -195,7 +204,6 @@ function $RootScopeProvider(){
195204
child['this'] = child;
196205
child.$$listeners = {};
197206
child.$parent = this;
198-
child.$$asyncQueue = [];
199207
child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
200208
child.$$prevSibling = this.$$childTail;
201209
if (this.$$childHead) {
@@ -293,22 +301,57 @@ function $RootScopeProvider(){
293301
eq: !!objectEquality
294302
};
295303

296-
// in the case user pass string, we need to compile it, do we really need this ?
297-
if (!isFunction(listener)) {
298-
var listenFn = compileToFn(listener || noop, 'listener');
299-
watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
300-
}
304+
if (typeof watchExp == 'string') {
305+
// A watch can be a string in which case it is a path
306+
scope.$$observer.observePathValue(scope, watchExp, function(newValue, oldValue) {
307+
listener(newValue, oldValue, scope);
308+
});
309+
// This is a quick hack to prime the listener with initial value.
310+
scope.$evalAsync(function() {
311+
listener(scope.$eval(watchExp), undefined, scope);
312+
});
313+
} else if (watchExp.parts) {
314+
// A watch can be interpolation in which case watch the idividual paths.
315+
forEach(watchExp.parts, function(part) {
316+
if (part.exp) {
317+
scope.$$observer.observePathValue(scope, part.exp, function(newValue, oldValue) {
318+
listener(watchExp(scope), 'TOO LAZY TO IMPLEMENT', scope);
319+
});
320+
// This is a quick hack to prime the listener with initial value.
321+
scope.$evalAsync(function() {
322+
listener(watchExp(scope), undefined, scope);
323+
});
324+
}
325+
});
326+
} else if (typeof watchExp.exp == 'string') {
327+
// A watch can be pre-compiled expression, in which case use the uncompiled path.
328+
scope.$$observer.observePathValue(scope, watchExp.exp, function(newValue, oldValue) {
329+
listener(newValue, oldValue, scope);
330+
});
331+
// This is a quick hack to prime the listener with initial value.
332+
scope.$evalAsync(function() {
333+
listener(watchExp(scope), undefined, scope);
334+
});
335+
} else {
336+
// We can't use ObjectObserve, so go do normal stuff
337+
338+
// in the case user pass string, we need to compile it, do we really need this ?
339+
if (!isFunction(listener)) {
340+
var listenFn = compileToFn(listener || noop, 'listener');
341+
watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
342+
}
301343

302-
if (!array) {
303-
array = scope.$$watchers = [];
304-
}
305-
// we use unshift since we use a while loop in $digest for speed.
306-
// the while loop reads in reverse order.
307-
array.unshift(watcher);
344+
if (!array) {
345+
array = scope.$$watchers = [];
346+
}
347+
// we use unshift since we use a while loop in $digest for speed.
348+
// the while loop reads in reverse order.
349+
array.unshift(watcher);
308350

309-
return function() {
310-
arrayRemove(array, watcher);
311-
};
351+
return function() {
352+
arrayRemove(array, watcher);
353+
};
354+
}
312355
},
313356

314357
/**

test/ObjectObserve.html

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<!doctype html>
2+
<html ng-app>
3+
<head>
4+
<script src="../src/change_summary.js"></script>
5+
<script src="../src/dispatcher.js"></script>
6+
<script src="../src/angular-bootstrap.js"></script>
7+
<script>
8+
function Filter($scope) {
9+
$scope.versions = [
10+
{ version: '1.0.0', name: 'temporal-domination', date: '2012-06-13' },
11+
{ version: '1.0.0rc12', name: 'regression-extermination', date: '2012-06-12' },
12+
{ version: '1.0.0rc11', name: 'promise-resolution', date: '2012-06-10' },
13+
{ version: '1.0.0rc10', name: 'tesseract-giftwrapping', date: '2012-05-23' },
14+
{ version: '1.0.0rc9', name: 'eggplant-teleportation', date: '2012-05-14' },
15+
{ version: '1.0.0rc8', name: 'blooming-touch', date: '2012-05-06' },
16+
{ version: '1.0.0rc7', name: 'rc-generation', date: '2012-04-30' },
17+
{ version: 'v1.0.0rc6', name: 'runny-nose', date: '2012-04-20' },
18+
{ version: '1.0.0rc5', name: 'reality-distortion', date: '2012-04-12' },
19+
{ version: '1.0.0rc4', name: 'insomnia-induction', date: '2012-04-05' },
20+
{ version: '1.0.0rc3', name: 'barefoot-telepathy', date: '2012-03-29' },
21+
{ version: '1.0.0rc2', name: 'silence-absorption', date: '2012-03-20' },
22+
{ version: '1.0.0rc1', name: 'moiré-vision', date: '2012-03-13' },
23+
{ version: '0.10.6', name: 'bubblewrap-cape', date: '2012-01-17' },
24+
{ version: '0.10.5', name: 'steel-fist', date: '2011-11-08' },
25+
{ version: '0.10.4', name: 'human-torch', date: '2011-10-22' },
26+
{ version: '0.10.3', name: 'shattering-heartbeat', date: '2011-10-13' },
27+
{ version: '0.10.2', name: 'sneaky-seagull', date: '2011-10-08' },
28+
{ version: '0.10.1', name: 'inexorable-juggernaut', date: '2011-09-09' },
29+
{ version: '0.10.0', name: 'chicken-hands', date: '2011-09-02' },
30+
{ version: '0.9.19', name: 'canine-psychokinesis', date: '2011-08-20' },
31+
{ version: '0.9.18', name: 'jiggling-armfat', date: '2011-07-29' },
32+
{ version: '0.9.17', name: 'vegetable-reanimation', date: '2011-06-30' },
33+
{ version: '0.9.16', name: 'weather-control', date: '2011-06-07' },
34+
{ version: '0.9.15', name: 'lethal-stutter', date: '2011-04-11' },
35+
{ version: '0.9.14', name: 'key-maker', date: '2011-04-01' },
36+
{ version: '0.9.13', name: 'curdling-stare', date: '2011-03-13' },
37+
{ version: '0.9.12', name: 'thought-implanter', date: '2011-03-03' },
38+
{ version: '0.9.11', name: 'snow-maker ', date: '2011-02-08' },
39+
{ version: '0.9.10', name: 'flea-whisperer ', date: '2011-01-26' },
40+
{ version: '0.9.9', name: 'time-shift', date: '2011-01-13' },
41+
{ version: '0.9.8', name: 'astral-projection', date: '2010-12-23' },
42+
{ version: '0.9.7', name: 'sonic-scream', date: '2010-12-10' },
43+
{ version: '0.9.6', name: 'night-vision', date: '2010-12-06' },
44+
{ version: '0.9.5', name: 'turkey-blast', date: '2010-11-25' },
45+
{ version: '0.9.4', name: 'total-recall', date: '2010-11-18' },
46+
{ version: '0.9.3', name: 'cold-resistance', date: '2010-11-10' },
47+
{ version: '0.9.2', name: 'faunal-mimicry', date: '2010-11-03' },
48+
{ version: '0.9.1', name: 'repulsion-field', date: '2010-10-26' },
49+
{ version: '0.9.0', name: 'dragon-breath', date: '2010-10-20' }
50+
];
51+
52+
$scope.$watch('predicate', function(predicate) {
53+
$scope.filteredVersions = $scope.versions.filter(function(version) {
54+
return !predicate ||
55+
version.version.indexOf(predicate) != -1 ||
56+
version.name.indexOf(predicate) != -1 ||
57+
version.date.indexOf(predicate) != -1;
58+
});
59+
});
60+
61+
$scope.filteredVersions = $scope.versions;
62+
63+
}
64+
</script>
65+
</head>
66+
<body ng-controller="Filter">
67+
<label>Name:</label>
68+
<input type="text" ng-model="predicate" placeholder="Enter a name here">
69+
<hr>
70+
<h1>predicate: {{predicate}}!</h1>
71+
<table>
72+
<tr>
73+
<td>Date</td>
74+
<td>Version</td>
75+
<td>Code Name</td>
76+
</tr>
77+
<tr ng-repeat="release in filteredVersions">
78+
<td>{{release.date}}</td>
79+
<td>{{release.version}}</td>
80+
<td>{{release.name}}</td>
81+
</tr>
82+
</table>
83+
</body>
84+
</html>

0 commit comments

Comments
 (0)