Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit bcf72ca

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

File tree

13 files changed

+15
-541
lines changed

13 files changed

+15
-541
lines changed

Diff for: docs/content/guide/expression.ngdoc

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

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

Diff for: src/ng/compile.js

-4
Original file line numberDiff line numberDiff line change
@@ -1517,7 +1517,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15171517
parentSet(scope, parentValue = isolateScope[scopeName]);
15181518
}
15191519
}
1520-
parentValueWatch.$$unwatch = parentGet.$$unwatch;
15211520
return lastValue = parentValue;
15221521
}, null, parentGet.literal);
15231522
break;
@@ -1855,9 +1854,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
18551854
return function textInterpolateLinkFn(scope, node) {
18561855
var parent = node.parent(),
18571856
bindings = parent.data('$binding') || [];
1858-
// Need to interpolate again in case this is using one-time bindings in multiple clones
1859-
// of transcluded templates.
1860-
interpolateFn = $interpolate(text);
18611857
bindings.push(interpolateFn);
18621858
parent.data('$binding', bindings);
18631859
if (!hasCompileParent) safeAddClass(parent, 'ng-binding');

Diff for: src/ng/directive/ngBind.js

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

183183
var parsed = $parse(attr.ngBindHtml);
184-
function getStringValue() {
185-
var value = parsed(scope);
186-
getStringValue.$$unwatch = parsed.$$unwatch;
187-
return (value || '').toString();
188-
}
184+
function getStringValue() { return (parsed(scope) || '').toString(); }
189185

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

Diff for: 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) {

Diff for: src/ng/parse.js

+3-38
Original file line numberDiff line numberDiff line change
@@ -992,21 +992,13 @@ function $ParseProvider() {
992992
$parseOptions.csp = $sniffer.csp;
993993

994994
return function(exp) {
995-
var parsedExpression,
996-
oneTime;
995+
var parsedExpression;
997996

998997
switch (typeof exp) {
999998
case 'string':
1000999

1001-
exp = trim(exp);
1002-
1003-
if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
1004-
oneTime = true;
1005-
exp = exp.substring(2);
1006-
}
1007-
10081000
if (cache.hasOwnProperty(exp)) {
1009-
return oneTime ? oneTimeWrapper(cache[exp]) : cache[exp];
1001+
return cache[exp];
10101002
}
10111003

10121004
var lexer = new Lexer($parseOptions);
@@ -1019,40 +1011,13 @@ function $ParseProvider() {
10191011
cache[exp] = parsedExpression;
10201012
}
10211013

1022-
return oneTime || parsedExpression.constant ? oneTimeWrapper(parsedExpression) : parsedExpression;
1014+
return parsedExpression;
10231015

10241016
case 'function':
10251017
return exp;
10261018

10271019
default:
10281020
return noop;
10291021
}
1030-
1031-
function oneTimeWrapper(expression) {
1032-
var stable = false,
1033-
lastValue;
1034-
oneTimeParseFn.literal = expression.literal;
1035-
oneTimeParseFn.constant = expression.constant;
1036-
oneTimeParseFn.assign = expression.assign;
1037-
return oneTimeParseFn;
1038-
1039-
function oneTimeParseFn(self, locals) {
1040-
if (!stable) {
1041-
lastValue = expression.constant && lastValue ? lastValue : expression(self, locals);
1042-
oneTimeParseFn.$$unwatch = isDefined(lastValue);
1043-
if (oneTimeParseFn.$$unwatch && self && self.$$postDigestQueue) {
1044-
self.$$postDigestQueue.push(function () {
1045-
// create a copy if the value is defined and it is not a $sce value
1046-
if ((stable = isDefined(lastValue)) &&
1047-
(lastValue === null || !lastValue.$$unwrapTrustedValue)) {
1048-
lastValue = copy(lastValue, null);
1049-
}
1050-
});
1051-
}
1052-
}
1053-
return lastValue;
1054-
}
1055-
}
1056-
};
10571022
}];
10581023
}

Diff for: src/ng/rootScope.js

+10-25
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,14 @@ function $RootScopeProvider(){
344344
watcher.fn = noop;
345345
}
346346

347+
if (typeof watchExp == 'string' && get.constant) {
348+
var originalFn = watcher.fn;
349+
watcher.fn = function(newVal, oldVal, scope) {
350+
originalFn.call(this, newVal, oldVal, scope);
351+
arrayRemove(array, watcher);
352+
};
353+
}
354+
347355
if (!array) {
348356
array = scope.$$watchers = [];
349357
}
@@ -389,37 +397,24 @@ function $RootScopeProvider(){
389397
var deregisterFns = [];
390398
var changeCount = 0;
391399
var self = this;
392-
var unwatchFlags = new Array(watchExpressions.length);
393-
var unwatchCount = watchExpressions.length;
394400

395401
forEach(watchExpressions, function (expr, i) {
396-
var exprFn = $parse(expr);
397-
deregisterFns.push(self.$watch(exprFn, function (value, oldValue) {
402+
deregisterFns.push(self.$watch(expr, function (value, oldValue) {
398403
newValues[i] = value;
399404
oldValues[i] = oldValue;
400405
changeCount++;
401-
if (unwatchFlags[i] && !exprFn.$$unwatch) unwatchCount++;
402-
if (!unwatchFlags[i] && exprFn.$$unwatch) unwatchCount--;
403-
unwatchFlags[i] = exprFn.$$unwatch;
404406
}));
405407
}, this);
406408

407-
deregisterFns.push(self.$watch(watchGroupFn, function () {
409+
deregisterFns.push(self.$watch(function () {return changeCount;}, function () {
408410
listener(newValues, oldValues, self);
409-
if (unwatchCount === 0) {
410-
watchGroupFn.$$unwatch = true;
411-
} else {
412-
watchGroupFn.$$unwatch = false;
413-
}
414411
}));
415412

416413
return function deregisterWatchGroup() {
417414
forEach(deregisterFns, function (fn) {
418415
fn();
419416
});
420417
};
421-
422-
function watchGroupFn() {return changeCount;}
423418
},
424419

425420

@@ -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

Diff for: src/ng/sce.js

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

0 commit comments

Comments
 (0)