87

In AngularJS, how can I render a value without 2-way data binding? One may want to do this for performance reasons, or even rendering a value at a given point in time.

The following examples both use data binding:

<div>{{value}}</div>

<div data-ng-bind="value"></div>

How do I render value without any data binding?

isherwood
  • 58,414
  • 16
  • 114
  • 157
Blowsie
  • 40,239
  • 15
  • 88
  • 108
  • whats your input and output . plz explain – Nitish Kumar Sep 13 '13 at 15:52
  • 3
    Your examples are actually one-way data binding (model changes -> view updates). `ng-model` gives you two-way data binding: model changes -> view updates, view changes -> model updates. – Mark Rajcok Sep 13 '13 at 15:55
  • 1
    updated. sorry i meant I want no data-binding at all – Blowsie Sep 13 '13 at 16:01
  • 10
    I don't think this question is terrible or deserved a downvote. It's actually really common to want to disable data binding to prevent unnecessary watches. – OverZealous Sep 13 '13 at 16:01
  • @OverZealous if this question is about disabling data binding then it's a duplicate because i have asked that question here. http://stackoverflow.com/questions/18240168/genuinely-stop-a-element-from-binding-unbind-an-element-angularjs – iConnor Sep 13 '13 at 17:02
  • 4
    UPDATE: anyone reading this article will probaly find this video extremely useful. http://www.youtube.com/watch?v=zyYpHIOrk_Y – Blowsie Jan 24 '14 at 12:24

5 Answers5

141

Angular 1.3+

In 1.3, Angular has supported this using the following syntax.

<div>{{::message}}</div>

As mentioned in this answer.


Angular 1.2 and below

This is simple and doesn't need a plugin. Check this out.

This small directive will easily accomplish what you are trying to achieve

app.directive('bindOnce', function() {
    return {
        scope: true,
        link: function( $scope ) {
            setTimeout(function() {
                $scope.$destroy();
            }, 0);
        }
    }
});

You can bind once like this

<div bind-once>I bind once - {{message}}</div>

You can bind like normal

<div ng-bind="message" bind-once></div>

Demo: http://jsfiddle.net/fffnb/

Some of you may be using angular batarang, and as mentioned in the comments if you use this directive the element still shows as binding when it is not, I am pretty sure this has something to do with the classes that are attached to the element so try this, it should work (not tested). Let me know in the comments if it worked for you.

app.directive('bindOnce', function() {
    return {
        scope: true,
        link: function( $scope, $element ) {
            setTimeout(function() {
                $scope.$destroy();
                $element.removeClass('ng-binding ng-scope');
            }, 0);
        }
    }
});

@x0b: If you have OCD and you want to remove the empty class attribute do this

!$element.attr('class') && $element.removeAttr('class')
Community
  • 1
  • 1
iConnor
  • 19,997
  • 14
  • 62
  • 97
  • im yet to test the plugin, but I would assume the AngularJS chrome tools would not show the bind-once element as a binding, where as your example does. Interesting approach tho, I will test both approaches soon. – Blowsie Sep 13 '13 at 17:03
  • See this - it shows both as bindings https://dl.dropboxusercontent.com/u/14037764/Development/stackoverflow/angular/bindings-dev-tools.PNG – Blowsie Sep 13 '13 at 17:09
  • 1
    Without a doubt that is because if the ng-binding class that you can easily remove – iConnor Sep 13 '13 at 17:12
  • In the setTimeout remove the ng-binding and ng-scope classes – iConnor Sep 13 '13 at 17:14
  • 4
    This is great and much simpler than the bindonce plugin. I added an ability to wait for a condition before destroying the scope and it's really helpful. thanks. – Yaron Jan 03 '14 at 12:59
  • Wouldn't it be better to use `$timeout` to make it testable? – Robert Koritnik Jan 07 '14 at 11:45
  • @RobertKoritnik I can't see any advantages using `$timeout`, it will be doing more operations that I don't think are needed, of course you can do though, if you wish – iConnor Jan 07 '14 at 12:22
  • For those who want to remove a redundant empty class attribute then add this: if ($element.attr('class') === '') { $element.removeAttr('class'); } – x0b Apr 23 '14 at 12:32
  • @x0b cheers, I've done that before, it does get annoying I'll add a reference in the answer. – iConnor Apr 23 '14 at 16:45
  • How does this work for deferred objects on a scope? The bind-once is destroying the scope before the property I want to bind-once is resolved. – SirTophamHatt Aug 29 '14 at 17:21
  • @SirTophamHatt that defeats the object of "bind once", if your property isn't resolved then the first bound value will be undefined, you're going to need a different solution for a different problem – iConnor Aug 30 '14 at 15:59
  • 1
    @Connor I disagree. For example, I'm receiving a video object ($scope.video) from a REST API and I want one-time binding of the video title ($scope.video.title). Even if I resolve the promise BEFORE adding it to the scope in the controller, I still have to declare ng-bind="video.title" bind-once on the DOM. Now, before the promise gets resolved, video.title is undefined, and the scope gets destroyed before video.title is defined. A solution I have for this is to wrap the elements in some type of loading/init flag, ng-if="someLoadingFlag", but it's a poor pattern. – SirTophamHatt Aug 31 '14 at 20:39
  • @Connor - the "$scope.$destroy" will not remove the $watch (on angular 1.2). Any ideas how this could be fixed? see http://stackoverflow.com/questions/27400422/angularjs-scope-destroy-doesnt-remove-watchers – Yaniv Efraim Dec 10 '14 at 12:37
  • After struggling for a while, I made a Question regarding the Batarang thing: http://stackoverflow.com/questions/32012004/angular-one-way-data-binding-not-working/32012006#32012006 – Augustin Riedinger Aug 14 '15 at 14:53
49

It looks like Angular 1.3 (starting with beta 10) has one-time binding built in:

https://docs.angularjs.org/guide/expression#one-time-binding

One-time binding

An expression that starts with :: is considered a one-time expression. One-time expressions will stop recalculating once they are stable, which happens after the first digest if the expression result is a non-undefined value (see value stabilization algorithm below).

chakming
  • 390
  • 5
  • 18
Karen Zilles
  • 7,633
  • 3
  • 34
  • 33
  • 1
    This answer again and again. I can't praise you enough Karl! I highly recommend the agressive usage of this feature wherever it makes sense. – XDS Jul 08 '14 at 13:21
  • 1
    Wow I am really glad I scrolled down. I'm going to ask Connor to reference this in his accepted answer. – JSager Jul 28 '14 at 19:49
  • I have a table / list with 2000 lines and using the one-time-binding operator my app gets extremely slow when first showing / rendering the list. So slow, that the browser asks me two or three times if I want to stop executing the script! – Billy G Aug 04 '14 at 14:11
  • @billy-g Can you post a jsfiddle or plunker illustrating the issue? – James Daily Aug 12 '14 at 17:55
  • @James Daily: Here is the "normal" case http://plnkr.co/edit/rCRP0T5fSgNIllx7F27y and here the "one-time expression" case http://plnkr.co/edit/Rd5VBVjkcX3sTJYGypUr but... I can not reproduce it there. Anyway, it isn't faster with the "one-time expression" and I have to do more investigation to find why it happens in my environment (I use 1.3 beta 18 of angularjs) – Billy G Aug 16 '14 at 18:31
20

Use the bindonce module. You'll need to include the JS file and add it as a dependency to your app module:

var myApp = angular.module("myApp", ['pasvaz.bindonce']);

This library allows you to render items that are bound only once — when they are first initialized. Any further updates to those values will be ignored. It's a great way to reduce the number of watches on the page for things that won't change after they are rendered.

Usage example:

<div bo-text="value"></div>

When used like this, the property under value will be set once it is available, but then the watch will be disabled.

OverZealous
  • 39,252
  • 15
  • 98
  • 100
  • 1
    I was about to write an answer "write your own directive ...", but it looks like someone has already done that for us, nice. – Mark Rajcok Sep 13 '13 at 16:06
  • 3
    Bindonce is useful enough that it could be included as a built-in optional library, like `$resource`. – OverZealous Sep 13 '13 at 16:07
  • 6
    this is what i was looking for, however I was expecting something like this to be built into angular! – Blowsie Sep 13 '13 at 16:15
7

Comparison between @OverZealous and @Connor answers :

With the traditional ngRepeat of angular : 15s for 2000 rows and 420mo of RAM (Plunker)

With ngRepeat and the module of @OverZealous : 7s for 2000 rows and 240mo of RAM(Plunker)

With ngRepeat and the directive of @Connor : 8s for 2000 rows and 500mo of RAM (Plunker)

I made my tests with Google Chrome 32.

Blowsie
  • 40,239
  • 15
  • 88
  • 108
Gabriel
  • 3,633
  • 1
  • 23
  • 13
  • 1
    Would be nice to also have `angular-once` compared. Thanks. – alecxe Sep 10 '14 at 01:15
  • @alecxe : I planned to do the tests when a stable build of AngularJS 1.3 be published. – Gabriel Sep 10 '14 at 06:12
  • Thanks, don't forget to include [`angular-once`](https://github.com/tadeuszwojcik/angular-once) package (I've posted it as an alternative option here). – alecxe Sep 11 '14 at 03:05
5

As an alternative, there is angular-once package:

If you use AngularJS, have performance issues and need to display lots of readonly data, this project is for you!

angular-once was actually inspired by bindonce and provides similar once-* attributes:

<ul>
    <li ng-repeat="user in users">
      <a once-href="user.profileUrl" once-text="user.name"></a>
        <a once-href="user.profileUrl"><img once-src="user.avatarUrl"></a>
        <div once-class="{'formatted': user.description}" once-bind="user.description"></div>
    </li>
</ul>
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195