When I first starting reading about one-time data bindings in AngularJS 1.3, it wasn't immediately clear to me how they worked or how to use them. When I finally took a look at the source code last night, it finally clicked: one-time bindings aren't a "single thing." Rather, it's the interplay between the $parse() service and the various $watch-bindings. Once I understood this, the one-time data bindings made sense.
As of AngularJS 1.3, you can use the "::" token to create one-time data bindings. These are bindings that deregister their own $watch() functions once the value has stabilized (which basically means the value is defined). But, to me, this doesn't clarify the usage of the "::" token. The most confusing part of the AngularJS documentation, regarding one-time data bindings, is the diversity of examples. Both of these attributes apparently use one-time data bindings:
- ng-repeat="item in ::items"
When I look at this, it becomes unclear as to where I need to put the "::". Is it at the beginning of the attribute? Or before any variable?
It turns out, the answer is "neither". The placement of the "::" in the attribute is completely irrelevant. The only thing that matters is how the attribute value is transformed into $watch() bindings by the consuming directive. This isn't clear, however, until you look at the source code and see what the one-time data binding is actually doing.
The one-time data bindings require two parts to function. It needs a parsed expression and a $watch() binding. The $parse() service is the first key ingredient. In order to get a one-time data binding, you have call $parse() - implicitly or explicitly - with a string that starts with "::":
$parse( ":: then anything else you want after" )
When $parse() detects ":" in characters 0 and 1, it will strip them out and flag the expression as a one-time expression. This does not affect the functionality of the computed accessor; however, it will assign a "watch delegate" to the accessor which, subsequently, will be consumed by any $watch() bindings that monitor that accessor.
This "watch delegate" acts as a proxy to your $watch() handler and calls its own deregistration function once the value of the expression is computed (and is not undefined).
So, going back to the attribute example above:
ng-repeat="item in ::items"
... this works because the "::items" string is passed into Scope.$watchCollection(), which will, in turn, pass it to $parse(), which will detect the leading "::" token, flag it as a one-time binding, parse it and provide a "one-time watch delegate" which will be consumed by $watchCollection().
To see this in action, I tried to create a demo that explicitly showcases the interplay between the $parse() service and the $watch() method:
Once we have a our parsed "one time" expression, I go about changing the relevant Scope value. Then, we see how this affects both the computed accessor and the $watch() bindings. I've create two kinds of $watch() bindings to demonstrate that $watch() passes its expression off to the $parse() service behind the scenes (and that the delivery mechanism doesn't matter).
When we run this code, we get the following console output:
From parse: My Friend, Kim
From watch: My Friend, Kim
From watch (2): Kim
From parse: My Friend, Sarah
As you can see, both $watch() bindings (using either the computed accessor or the raw string expression) were invoked for the first value of "friend". However, when I changed the value to "Sarah", and triggered another digest, the computed accessor returned the updated value while neither $watch() callbacks were invoked. This is because both $watch() bindings deregistered themselves after the first $digest phase completed with a defined value.
In AngularJS 1.3, you can now use the "::" token to define one-time data bindings. However, this only works if the given expression is passed to the $parse() service and then consumed by one of the $watch methods (ie, $watch(), $watchCollection(), etc.). This is particularly important to understand, especially when you start creating your own directives which can accept complex statements (like the ngRepeat statement, "item in items").
I did a quick follow-up on one-time data bindings to see how they work with object-literal expressions:
Even if you've never set up an object-literal $watch() binding explicitly, this is exactly what ngClass and ngStyle do; so, if you use those directives, this exploration is meaningful to you.
Thank you for the excellent article. Here is a problem that I am facing with one time bindings. I want to create a large table to entries. I also want to be able to add entries to that table as well as inline-edit individual rows on that table (without having to reload the table entries). However, since the table is large, I want to use one-time binding to render it.
Is there some way to achieve this while still using the one-time bindings?
We are using an api [bindonce] for one time binding in `ng-repat` along with its other syntax e.g. `bo-if` for `ng-if` or `bo-bind` for `ng-bind`
As, We see here. AngularJS 1.3 on wards has built in syntax `ng-repeat="item in ::items"`.
So, With newer code we are using this new syntax. But, Out of curiosity,
Can this bindonce API cause memory leaks.?
Would also love to know the answer to your question