2018-06-13 admin


什么是angular-bindonce,Zero watchers binding directives for AngularJS


<li ng-repeat="person in Persons">
	<a ng-href="#/people/{{}}"><img ng-src="{{person.imageUrl}}"></a>
	<a ng-href="#/people/{{}}"><span ng-bind=""></span></a>
	<p ng-class="{'cycled':person.generated}" ng-bind-html-unsafe="person.description"></p>


Angular internally creates a `$watch` for each `ng-*` directive in order to keep the data up to date, so in this example just for displaying few info it creates 6 + 1 _(ngRepeatWatch)_ watchers per `person`, even if the `person` is supposed to remain the same once shown. Iterate this amount for each person and you can have an idea about how easy is to reach 2000 watchers. Now if you need it because those data could change while you show the page or are bound to some models, it's ok. But most of the time they are static data that don't change once rendered. This is where **bindonce** can really help you.

The above example done with **bindonce**:

	<li bindonce ng-repeat="person in Persons">
		<a bo-href="'#/people/' +"><img bo-src="person.imageUrl"></a>
		<a bo-href="'#/people/' +" bo-text=""></a>
		<p bo-class="{'cycled':person.generated}" bo-html="person.description"></p>

Now this example uses 0 watches per person and renders exactly the same result as the above that uses ng-*. (Angular still uses 1 watcher for ngRepeatWatch)

The smart approach

OK until here nothing completely new, with a bit of efforts you could create your own directive and render the person inside the link function, or you could use watch fighters that has a similar approach, but there is still one problem that you have to face and bindonce already handles it: the existence of the data when the directive renders the content. Usually the directives, unless you use watchers or bind their attributes to the scope (still a watcher), render the content when they are loaded into the markup, but if at that given time your data is not available, the directive can’t render it. Bindonce can wait until the data is ready before to rendering the content. Let’s take a look at the follow snippet to better understand the concept:

<span my-custom-set-text="Person.firstname"></span>
<span my-custom-set-text="Person.lastname"></span>
angular.module('testApp', [])
.directive('myCustomSetText', function () {
	return {
		link:function (scope, elem, attr, ctrl) {

This basic directive works as expected, it renders the Person data without using any watchers. However, if Person is not yet available inside the $scope when the page is loaded (say we get Person via $http or via $resource), the directive is useless, scope.$eval(attr.myCustomSetText) simply renders nothing and exits.

Here is how we can solve this issue with bindonce:

<div bindonce="Person" bo-title="Person.title">
	<span bo-text="Person.firstname"></span>
	<span bo-text="Person.lastname"></span>
	<img bo-src="Person.picture" bo-alt="Person.title">
	<p bo-class="{'fancy':Person.isNice}" bo-html="Person.story"></p>

bindonce="Person" does the trick, any bo-* attribute belonging to bindonce waits until the parent bindonce="{somedata}" is validated and then renders its content. Once the scope contains the value Person then each bo-* child gets filled with the proper values. In order to accomplish this task, bindonce uses just one temporary watcher, no matters how many children need to be rendered. As soon as it gets Person the watcher is promptly removed. If the $scope already contains the data bindonce is looking for, then it doesn’t create the temporary watcher and simply starts rendering its children.

You may have noticed that the first example didn’t assign any value to the bindonce attribute:

	<li bindonce ng-repeat="person in Persons">

when used with ng-repeat bindonce doesn’t need to check if person is defined because ng-repeat creates the directives only when person exists. You could be more explicit: <li bindonce="person" ng-repeat="person in Persons">, however assigning a value to bindonce in an ng-repeat won’t make any difference.


Some directives (ng-href, ng-src) use interpolation, ie: ng-href="/profile/{{User.profileId}}". Both ng-href and ng-src have the bo-* equivalent directives: bo-href-i and bo-src-i (pay attention to the -i, it stands for interpolate). As expected they don’t use watchers however Angular creates one watcher per interpolation, for instance bo-href-i="/profile/{{User.profileId}}" sets the element’s href once, as expected, but Angular keeps a watcher active on {{User.profileId}} even if bo-href-i doesn’t use it. That’s why by default the bo-href doesn’t use interpolation or watchers. The above equivalent with 0 watchers would be bo-href="'/profile/' + User.profileId". Nevertheless, bo-href-i and bo-src-i are still maintained for compatibility reasons.


Almost every bo-* directive replace the equivalent ng-* and works in the same ways, except it is evaluated once. Consequentially you can use any valid angular expression, including filters. This is an example how to use a filter:

<div bindonce="Person">
	<span bo-bind="Person.bill | currency:'USD$'"></span>

Attribute Usage

Directive Description Example
bindonce="{somedata}" bindonce is the main directive. {somedata} is optional, and if present, forces bindonce to wait until somedata is defined before rendering its children <div bindonce="Person">...<div>
bo-if = "condition" equivalent to ng-if but doesn’t use watchers <ANY bo-if="Person.isPublic"></ANY>
bo-switch = "expression" equivalent to ng-switch but doesn’t use watchers <div bo-switch="Person.isPublic"> <span bo-switch-when="'yes">public</span> <span bo-switch-default>private</span> </div>
bo-show = "condition" equivalent to ng-show but doesn’t use watchers <ANY bo-show="Person.isPublic"></ANY>
bo-hide = "condition" equivalent to ng-hide but doesn’t use watchers <ANY bo-hide="Person.isPrivate"></ANY>
bo-disabled = "condition" equivalent to ng-disabled but doesn’t use watchers <ANY bo-disabled="Person.isUnavailable"></ANY>
bo-text = "text" evaluates “text” and print it as text inside the element <span bo-text=""></span>
bo-bind = "text" alias for bo-text, equivalent to ng-bind but doesn’t use watchers <span bo-bind=""></span>
bo-html = "markup" evaluates “markup” and render it as html inside the element bo-html="Person.description"
bo-href-i = "url"
use bo-href instead equivalent to ng-href.

Heads up! Using interpolation {{}} it creates one watcher: bo-href-i="/p/{{}}". Use bo-href to avoid the watcher: bo-href="'/p/' +" | <a bo-href-i="/profile{{}}"></a> | | bo-href = "url" | similar to ng-href but doesn’t allow interpolation using {{}} like ng-href. Heads up! You can’t use interpolation {{}} inside the url, use bo-href-i for that purpose | <a bo-href="'/profile' +"></a> or <a bo-href="link" bo-text="Link"></a> | | bo-src-i = "url" use bo-src instead | equivalent to ng-src. Heads up! It creates one watcher | <img bo-src-i="{{picture}}" bo-alt="title"> | | bo-src = "url" | similar to ng-src but doesn’t allow interpolation using {{}} like ng-src. Heads up! You can’t use interpolation {{}}, use bo-src-i for that purpose | <img bo-src="picture" bo-alt="title"> | | bo-class = "object/string" | equivalent to ng-class but doesn’t use watchers | <span bo-class="{'fancy':Person.condition}"> | | bo-alt = "text" | evaluates “text” and render it as alt for the element | <ANY bo-alt="title"> | | bo-title = "text" | evaluates “text” and render it as title for the element | <ANY bo-title="title"> | | bo-id = "#id" | evaluates “#id” and render it as id for the element | <ANY bo-id="id"> | | bo-style = "object" | equivalent to ng-style but doesn’t use watchers | <ANY bo-style="{'color':Person.color}"> | | bo-value = "expression" | evaluates “expression” and render it as value for the element | <input type="radio" bo-value="value"> | | bo-attr bo-attr-foo = "text" | evaluates “text” and render it as a custom attribute for the element | `<div bo-attr bo-attr-foo=“bar”>


转载请注明:文章转载自 JavaScript中文网 []