18

I'm trying to find out how I can stop a DOM element from binding data from the scope in angular.

I know that you could do this with if statements and all, but is there a genuine & permanent way to stop binding a element in angular but keep the content that was added?

So say i have this

<div ng-bind=​"content" class=​"ng-binding">​Welcome​</div>​

And i change the model so that the div changes to this.

<div ng-bind=​"content" class=​"ng-binding">​Welcome​ World</div>​

Then I click the button that will unbind it, so if I change the model to 'Welcome Universe', I wan't the <div> to be the same as before. This

<div ng-bind=​"content" class=​"ng-binding">​Welcome​ World</div>​

I know there are many other ways to do this, but i don't know any way to genuinely unbind the element, without cloning it and replacing the old one looping through the attributes and text..ect

Demo thing: http://jsfiddle.net/a9tZY/

So, by doing this, it shouldn't affect the model or other elements that are binding to that model.

Long story short, Tell Angular to leave the element alone forever.

iConnor
  • 19,997
  • 14
  • 62
  • 97
  • What's the use case for this? – Christopher Marshall Aug 14 '13 at 19:09
  • I'm actually using `ng-style` and I wan't to stop binding on certain elements but continue binding on other elements, I used a simple text example so it's easier to understand, but the overall result will stop anything binding to that element. – iConnor Aug 14 '13 at 19:14
  • This post might be useful: http://stackoverflow.com/questions/13651578/how-to-unwatch-an-expression/ – Sharondio Aug 14 '13 at 19:28
  • This is interesting because there is a `$$watchers` object in the scope which holds all of the watchers and you could unbind it that way, but I can't find a way to identify what watcher is which, @Sharondio thanks for link, same sort of question, but not what I'm looking for. – iConnor Aug 14 '13 at 19:51
  • I really hope that this is **possible** – iConnor Aug 14 '13 at 23:01
  • With angular 1.3 you get {{::text }} syntax which does extactly that – frictionlesspulley Oct 08 '14 at 16:05

3 Answers3

19

UPDATE

The way to do this is to create a new scope on the element with a directive like so.

yourModule.directive('unbindable', function(){
    return { scope: true };
});

And apply it to your element like so

<div unbindable id="yourId"></div>

Then to unbind this element from any updates you do this.

angular.element( document.getElementById('yourId') ).scope().$destroy();

Done, here's a demo.

Demo: http://jsfiddle.net/KQD6H/

So this creates a new scope on the element and only works because all scopes inherit all data from their parent scopes. so the scope is basically the same as the parent scope, but allows you to destroy the scope without affecting the parent scope. Because this element was given it's own scope, when you destroy it it doesn't get the parent scope back like all of the other elements, if that makes sense 0.o

Everything below this line was my original answer,I'll leave it here incase someone prefers this way


I have managed to achieve this genuinely with a unbindable directive. When you have the unbinable directive set up on the element all that is required to unbind the element is this.

yourElement.attr('unbind', 'true'); // Ref 1
$scope.$broadcast('unbind'); // Ref 2

Here is the directive.

app.directive('unbindable', function(){
    return {
        scope: true, // This is what lets us do the magic.
        controller: function( $scope, $element){ 
            $scope.$on('unbind', function(){ // Ref 3
                if($element.attr('unbind') === 'true'){ // Ref 4
                    window.setTimeout(function(){ $scope.$destroy() }, 0);//Ref 5
                }
            });
        }
    }
});

and you set your element up like this.

<h1 unbindable></h1>

So whenever you add the unbind="true" attribute to the h1 and broadcast unbind the element will be unbind-ed

REF-1: Add the unbind true attribute to the element so that the directive knows what element you are unbinding.

REF-2: Broadcast the unbind event across the scopes so that the directive knows that you want to unbind a element - Make sure you add the attribute first. --- Depending on your app layout, you might need to use $rootScope.$broadcast

REF-3: When the unbind event is broadcasted

REF-4: If the element associated with the directive has a true unbind attribute

REF-5: Then destroy the scope made by the directive. We have to use setTimeout because I think angular tries to do something after the $on event and we get a error, so using setTimeout will prevent that error. Although it fires instantly.

This works on multiple elements, here is a nice demo.

Demo: http://jsfiddle.net/wzAXu/2/

Community
  • 1
  • 1
iConnor
  • 19,997
  • 14
  • 62
  • 97
  • That is pretty sweet. – Sharondio Aug 15 '13 at 17:29
  • so you need the directive to create the element scope? – Sharondio Aug 15 '13 at 17:43
  • It is weird because the element prototype includes .scope(). You'd think that the element scope was there regardless and the directive just grabs it. But maybe it requires the directive to expose it. – Sharondio Aug 15 '13 at 18:33
  • @Sharondio I think you will find that with the directive scope: true, it creates a new scope for the element, but the scopes inherit from the parent scopes anyway so the new scope still inherits the parent scope data, but when you remove the scope, you are removing the new scope and therefore that element no longer belongs in any scope, if that makes sense? – iConnor Aug 15 '13 at 19:08
  • 1
    Beware, that's not gonna work in production. When you disable debug info (`$compileProvider.debugInfoEnabled(true)`), the `.scope()` and `.isolateScope()` won't work. – davinov Oct 20 '15 at 13:16
  • @iConnor How would you re-bind the values? Is it possible? – adelriosantiago Oct 08 '17 at 08:34
3

This one got me curious, so I did some poking around. At first I tried the "unbind()" method suggested in the other answer, but that only worked with removing event handlers from the element when what you're actually trying to do is remove the angular scope from the element. There may be some neater hidden function in Angular to do this, but this works just fine too:

angular.element(document.getElementById('txtElem')).scope().$destroy();

This retains the model (and updates anything else still bound to it), but removes the binding from the element. Also, in your example above, there is no binding to remove because you aren't binding to any element, just displaying the model expression inline. My example shows this in action: http://jsfiddle.net/3jQMx/1/

Sharondio
  • 2,605
  • 13
  • 16
  • 1
    I know this is definitely the way to go, but it removes binding on all. http://jsfiddle.net/3jQMx/2/ – iConnor Aug 15 '13 at 13:31
  • Now I'm *really* curious. There has to be a simple way of doing this. Time to dig into the angular source.... – Sharondio Aug 15 '13 at 15:51
1

You can call the unbind method that stops listening to the element where the ng-model attribute is present. See fiddle: http://jsfiddle.net/jexgF/

angular.element(document.getElementById('txtElem')).unbind()

unbind removes all event listeners, so whenever any changes are made, it wont listen for those and hence not go through the angular loop. I have also assumed that you are not using jQuery, but if you are, you can use a better selector than document.getElementById

rajasaur
  • 5,340
  • 2
  • 27
  • 22
  • I wan't to remove the binding on this element `Stop me when your done: ` not the model – iConnor Aug 14 '13 at 19:40
  • not sure why it is downvoted, i did try to solve the problem and from the accepted answer, looks like was helpful somewhat. – rajasaur Aug 15 '13 at 08:43