Skip to content

Commit 8f86ab9

Browse files
committed
revert: feat(*): lazy one-time binding support
This reverts commit cee429f. See angular#7700 for a more performant approach for bind-once.
1 parent ff03698 commit 8f86ab9

13 files changed

+18
-521
lines changed

docs/content/guide/expression.ngdoc

-119
Original file line numberDiff line numberDiff line change
@@ -200,122 +200,3 @@ expose a `$event` object within the scope of that expression.
200200

201201
Note in the example above how we can pass in `$event` to `clickMe`, but how it does not show up
202202
in `{{$event}}`. This is because `$event` is outside the scope of that binding.
203-
204-
205-
## One-time binding
206-
207-
An expression that starts with `::` is considered a one-time expression. One-time expressions
208-
will stop recalculating once they are stable, which happens after the first digest if the expression
209-
result is a non-undefined value (see value stabilization algorithm below).
210-
211-
<example module="oneTimeBidingExampleApp">
212-
<file name="index.html">
213-
<div ng-controller="EventController">
214-
<button ng-click="clickMe($event)">Click Me</button>
215-
<p id="one-time-binding-example">One time binding: {{::name}}</p>
216-
<p id="normal-binding-example">Normal binding: {{name}}</p>
217-
</div>
218-
</file>
219-
<file name="script.js">
220-
angular.module('oneTimeBidingExampleApp', []).
221-
controller('EventController', ['$scope', function($scope) {
222-
var counter = 0;
223-
var names = ['Igor', 'Misko', 'Chirayu', 'Lucas'];
224-
/*
225-
* expose the event object to the scope
226-
*/
227-
$scope.clickMe = function(clickEvent) {
228-
$scope.name = names[counter % names.length];
229-
counter++;
230-
};
231-
}]);
232-
</file>
233-
<file name="protractor.js" type="protractor">
234-
it('should freeze binding after its value has stabilized', function() {
235-
var oneTimeBiding = element(by.id('one-time-binding-example'));
236-
var normalBinding = element(by.id('normal-binding-example'));
237-
238-
expect(oneTimeBiding.getText()).toEqual('One time binding:');
239-
expect(normalBinding.getText()).toEqual('Normal binding:');
240-
element(by.buttonText('Click Me')).click();
241-
242-
expect(oneTimeBiding.getText()).toEqual('One time binding: Igor');
243-
expect(normalBinding.getText()).toEqual('Normal binding: Igor');
244-
element(by.buttonText('Click Me')).click();
245-
246-
expect(oneTimeBiding.getText()).toEqual('One time binding: Igor');
247-
expect(normalBinding.getText()).toEqual('Normal binding: Misko');
248-
249-
element(by.buttonText('Click Me')).click();
250-
element(by.buttonText('Click Me')).click();
251-
252-
expect(oneTimeBiding.getText()).toEqual('One time binding: Igor');
253-
expect(normalBinding.getText()).toEqual('Normal binding: Lucas');
254-
});
255-
</file>
256-
</example>
257-
258-
259-
### Why this feature
260-
261-
The main purpose of one-time binding expression is to provide a way to create a binding
262-
that gets deregistered and frees up resources once the binding is stabilized.
263-
Reducing the number of expressions being watched makes the digest loop faster and allows more
264-
information to be displayed at the same time.
265-
266-
267-
### Value stabilization algorithm
268-
269-
One-time binding expressions will retain the value of the expression at the end of the
270-
digest cycle as long as that value is not undefined. If the value of the expression is set
271-
within the digest loop and later, within the same digest loop, it is set to undefined,
272-
then the expression is not fulfilled and will remain watched.
273-
274-
1. Given an expression that starts with `::` when a digest loop is entered and expression
275-
is dirty-checked store the value as V
276-
2. If V is not undefined mark the result of the expression as stable and schedule a task
277-
to deregister the watch for this expression when we exit the digest loop
278-
3. Process the digest loop as normal
279-
4. When digest loop is done and all the values have settled process the queue of watch
280-
deregistration tasks. For each watch to be deregistered check if it still evaluates
281-
to value that is not `undefined`. If that's the case, deregister the watch. Otherwise
282-
keep dirty-checking the watch in the future digest loops by following the same
283-
algorithm starting from step 1
284-
285-
286-
### How to benefit from one-time binding
287-
288-
When interpolating text or attributes. If the expression, once set, will not change
289-
then it is a candidate for one-time expression.
290-
291-
```html
292-
<div name="attr: {{::color}}">text: {{::name}}</div>
293-
```
294-
295-
When using a directive with bidirectional binding and the parameters will not change
296-
297-
```js
298-
someModule.directive('someDirective', function() {
299-
return {
300-
scope: {
301-
name: '=',
302-
color: '@'
303-
},
304-
template: '{{name}}: {{color}}'
305-
};
306-
});
307-
```
308-
309-
```html
310-
<div some-directive name=“::myName” color=“My color is {{::myColor}}”></div>
311-
```
312-
313-
314-
When using a directive that takes an expression
315-
316-
```html
317-
<ul>
318-
<li ng-repeat="item in ::items">{{item.name}};</li>
319-
</ul>
320-
```
321-

src/ng/compile.js

-4
Original file line numberDiff line numberDiff line change
@@ -1504,7 +1504,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15041504
parentSet(scope, parentValue = isolateScope[scopeName]);
15051505
}
15061506
}
1507-
parentValueWatch.$$unwatch = parentGet.$$unwatch;
15081507
return lastValue = parentValue;
15091508
}, null, parentGet.literal);
15101509
break;
@@ -1833,9 +1832,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
18331832
compile: valueFn(function textInterpolateLinkFn(scope, node) {
18341833
var parent = node.parent(),
18351834
bindings = parent.data('$binding') || [];
1836-
// Need to interpolate again in case this is using one-time bindings in multiple clones
1837-
// of transcluded templates.
1838-
interpolateFn = $interpolate(text);
18391835
bindings.push(interpolateFn);
18401836
safeAddClass(parent.data('$binding', bindings), 'ng-binding');
18411837
scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {

src/ng/directive/ngBind.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,7 @@ var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) {
174174
element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
175175

176176
var parsed = $parse(attr.ngBindHtml);
177-
function getStringValue() {
178-
var value = parsed(scope);
179-
getStringValue.$$unwatch = parsed.$$unwatch;
180-
return (value || '').toString();
181-
}
177+
function getStringValue() { return (parsed(scope) || '').toString(); }
182178

183179
scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) {
184180
element.html($sce.getTrustedHtml(parsed(scope)) || '');

src/ng/interpolate.js

-3
Original file line numberDiff line numberDiff line change
@@ -309,19 +309,16 @@ function $InterpolateProvider() {
309309

310310

311311
try {
312-
interpolationFn.$$unwatch = true;
313312
for (; i < ii; i++) {
314313
val = getValue(parseFns[i](context));
315314
if (allOrNothing && isUndefined(val)) {
316-
interpolationFn.$$unwatch = undefined;
317315
return;
318316
}
319317
val = stringify(val);
320318
if (val !== lastValues[i]) {
321319
inputsChanged = true;
322320
}
323321
values[i] = val;
324-
interpolationFn.$$unwatch = interpolationFn.$$unwatch && parseFns[i].$$unwatch;
325322
}
326323

327324
if (inputsChanged) {

src/ng/parse.js

+3-40
Original file line numberDiff line numberDiff line change
@@ -1000,21 +1000,13 @@ function $ParseProvider() {
10001000
$parseOptions.csp = $sniffer.csp;
10011001

10021002
return function(exp) {
1003-
var parsedExpression,
1004-
oneTime;
1003+
var parsedExpression;
10051004

10061005
switch (typeof exp) {
10071006
case 'string':
10081007

1009-
exp = trim(exp);
1010-
1011-
if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
1012-
oneTime = true;
1013-
exp = exp.substring(2);
1014-
}
1015-
10161008
if (cache.hasOwnProperty(exp)) {
1017-
return oneTime ? oneTimeWrapper(cache[exp]) : cache[exp];
1009+
return cache[exp];
10181010
}
10191011

10201012
var lexer = new Lexer($parseOptions);
@@ -1027,43 +1019,14 @@ function $ParseProvider() {
10271019
cache[exp] = parsedExpression;
10281020
}
10291021

1030-
if (parsedExpression.constant) {
1031-
parsedExpression.$$unwatch = true;
1032-
}
1033-
1034-
return oneTime ? oneTimeWrapper(parsedExpression) : parsedExpression;
1022+
return parsedExpression;
10351023

10361024
case 'function':
10371025
return exp;
10381026

10391027
default:
10401028
return noop;
10411029
}
1042-
1043-
function oneTimeWrapper(expression) {
1044-
var stable = false,
1045-
lastValue;
1046-
oneTimeParseFn.literal = expression.literal;
1047-
oneTimeParseFn.constant = expression.constant;
1048-
oneTimeParseFn.assign = expression.assign;
1049-
return oneTimeParseFn;
1050-
1051-
function oneTimeParseFn(self, locals) {
1052-
if (!stable) {
1053-
lastValue = expression(self, locals);
1054-
oneTimeParseFn.$$unwatch = isDefined(lastValue);
1055-
if (oneTimeParseFn.$$unwatch && self && self.$$postDigestQueue) {
1056-
self.$$postDigestQueue.push(function () {
1057-
// create a copy if the value is defined and it is not a $sce value
1058-
if ((stable = isDefined(lastValue)) && !lastValue.$$unwrapTrustedValue) {
1059-
lastValue = copy(lastValue, null);
1060-
}
1061-
});
1062-
}
1063-
}
1064-
return lastValue;
1065-
}
1066-
}
10671030
};
10681031
}];
10691032
}

src/ng/rootScope.js

+10-25
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,14 @@ function $RootScopeProvider(){
346346
watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
347347
}
348348

349+
if (typeof watchExp == 'string' && get.constant) {
350+
var originalFn = watcher.fn;
351+
watcher.fn = function(newVal, oldVal, scope) {
352+
originalFn.call(this, newVal, oldVal, scope);
353+
arrayRemove(array, watcher);
354+
};
355+
}
356+
349357
if (!array) {
350358
array = scope.$$watchers = [];
351359
}
@@ -391,37 +399,24 @@ function $RootScopeProvider(){
391399
var deregisterFns = [];
392400
var changeCount = 0;
393401
var self = this;
394-
var unwatchFlags = new Array(watchExpressions.length);
395-
var unwatchCount = watchExpressions.length;
396402

397403
forEach(watchExpressions, function (expr, i) {
398-
var exprFn = $parse(expr);
399-
deregisterFns.push(self.$watch(exprFn, function (value, oldValue) {
404+
deregisterFns.push(self.$watch(expr, function (value, oldValue) {
400405
newValues[i] = value;
401406
oldValues[i] = oldValue;
402407
changeCount++;
403-
if (unwatchFlags[i] && !exprFn.$$unwatch) unwatchCount++;
404-
if (!unwatchFlags[i] && exprFn.$$unwatch) unwatchCount--;
405-
unwatchFlags[i] = exprFn.$$unwatch;
406408
}));
407409
}, this);
408410

409-
deregisterFns.push(self.$watch(watchGroupFn, function () {
411+
deregisterFns.push(self.$watch(function () {return changeCount;}, function () {
410412
listener(newValues, oldValues, self);
411-
if (unwatchCount === 0) {
412-
watchGroupFn.$$unwatch = true;
413-
} else {
414-
watchGroupFn.$$unwatch = false;
415-
}
416413
}));
417414

418415
return function deregisterWatchGroup() {
419416
forEach(deregisterFns, function (fn) {
420417
fn();
421418
});
422419
};
423-
424-
function watchGroupFn() {return changeCount;}
425420
},
426421

427422

@@ -566,7 +561,6 @@ function $RootScopeProvider(){
566561
}
567562
}
568563
}
569-
$watchCollectionWatch.$$unwatch = objGetter.$$unwatch;
570564
return changeDetected;
571565
}
572566

@@ -662,7 +656,6 @@ function $RootScopeProvider(){
662656
dirty, ttl = TTL,
663657
next, current, target = this,
664658
watchLog = [],
665-
stableWatchesCandidates = [],
666659
logIdx, logMsg, asyncTask;
667660

668661
beginPhase('$digest');
@@ -713,7 +706,6 @@ function $RootScopeProvider(){
713706
logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
714707
watchLog[logIdx].push(logMsg);
715708
}
716-
if (watch.get.$$unwatch) stableWatchesCandidates.push({watch: watch, array: watchers});
717709
} else if (watch === lastDirtyWatch) {
718710
// If the most recently dirty watcher is now clean, short circuit since the remaining watchers
719711
// have already been tested.
@@ -760,13 +752,6 @@ function $RootScopeProvider(){
760752
$exceptionHandler(e);
761753
}
762754
}
763-
764-
for (length = stableWatchesCandidates.length - 1; length >= 0; --length) {
765-
var candidate = stableWatchesCandidates[length];
766-
if (candidate.watch.get.$$unwatch) {
767-
arrayRemove(candidate.array, candidate.watch);
768-
}
769-
}
770755
},
771756

772757

src/ng/sce.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -787,9 +787,7 @@ function $SceProvider() {
787787
return parsed;
788788
} else {
789789
return function sceParseAsTrusted(self, locals) {
790-
var result = sce.getTrusted(type, parsed(self, locals));
791-
sceParseAsTrusted.$$unwatch = parsed.$$unwatch;
792-
return result;
790+
return sce.getTrusted(type, parsed(self, locals));
793791
};
794792
}
795793
};

0 commit comments

Comments
 (0)