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