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.
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/
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/
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
Related
I'm creating a custom Angular directive for a slide in menu which needs to watch a couple of attributes and one of those attributes needs to be two way bound to the main controller scope (sometimes). However, sometimes the attribute will not be added by the developer so it needs to be added automatically and set to the default (false). So, the directive can be used like this.
<slide-menu position="right" is-open="menuIsOpen"></slide-menu>
or like this:
<slide-menu></slide-menu>
When used the first way the main controller will be able to open and close the menu by changing the value of the boolean $scope.menuIsOpen.
When used without supplying the is-open attribute it should default to false and is obviously used internally and by a child toggle directive.
An additional complication is that whether the attribute is supplied by the developer or not it should exist in the DOM. so in the second example above the directive would set itself to false by default and add the attribute is-open="false" to the DOM?
The reason for requiring is-open="false/true" in the DOM at all times is that the menu is actually operated using CSS tansitions which use the following selector:
slide-menu[is-active="true"]{
// Slide the menu in using transforms/transitions
}
There is a jsfiddle here which shows how far I have got.
http://jsfiddle.net/jonhobbs/gEPvE/
Obviously it doesn't work, but it shows how I have tried to set a default and how I have tried to use # and & on the isolated scope for a one time binding (the menu position) and a 2 way bound expression for the is-open variable.
I'm clearly a long way from achieving what I need but any advice would really be appreciated.
Have a look at this fiddle http://jsfiddle.net/gEPvE/38/
I took the one you started and updated it to act like you specified.
You can make a two way binding value optional by adding a ? on the scope definition.
Like this
{
scope: {
'isOpen':'=?'
}
}
Now the is-open attribute is optional.
Then you can set the default value in the directive controller, like you had started to do.
Next, in order to synchronize the DOM attribute with the scope value you can use $watch.
$scope.$watch('isOpen', function(val) {
$element.attr('is-open', val);
});
Finally, I changed the second 'slideMenuToggle' directive to wrap/transclude its element in order to add an ng-click handler. This is mainly to avoid any nastiness with calling $scope.$apply yourself.
Let me know if that works for you.
EDIT
Answering your question in the comment, you can pass a value directly without having it be bound to the scope, you just need to wrap the value in quotes.
For example
<div ng-controller='ctrl'>
<hello world='imOnScope'></hello>
</div>
Assuming 'hello' is a directive with a scope of 'world': '=?' then angular will assign a reference to the parent scope's 'imOnScope' object to the directive's $scope.world member, allowing a two way binding scenario.
To just provide a value directly you may do something like this
<div ng-controller="ctrl">
<hello world="'directValue'"></hello>
</div>
In this scenario angular will just assign 'directValue' to the directive's $scope.world member.
You need to add ngTouch to your module.
var app = angular.module('app', ['ngTouch']);
And add this script:
http://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular-touch.js
The reason for requiring is-open="false/true" in the DOM at all times
is that the menu is actually operated using CSS tansitions which use
the following selector
Forcing directive attributes to be appropriate for css selectors is terrible idea. As you correctly stated, they are for developers. So add a class to the element dynamically.
It seems that you're misusing &, it would be ok to set up a callback, but since you don't do this, in its current state you can end up with one-way # with confidence.
I guess it can be something like this (just added ngTouch and ng-controller for parent scope).
You could replace
$scope.watch('isOpen', function () {
$element.toggleClass('opened', $scope.isOpen);
});
with
$scope.watch('isOpen', function () {
$attrs.isOpen = !!$scope.isOpen;
});
and get the behaviour you're asking for, easy as that. Ok, it is boolean now, and it reflects the scope, and you can use [is-open=true] selector. But guess what will happen with your binding? Broken. Fortunately, you can do
$scope.watch('isOpen', function () {
$element.attr('is-open', !!$scope.isOpen);
});
instead. Voila, we tricked Angular because it doesn't look after jqlite. But what will will happen with the binding when the directive will be re-compiled for any reason? Again, isOpen's binding is non-existing 'true' or 'false' scope variable. Broken.
It's pretty common thing, like if you click on inbox here here on stackoverflow. I'll be calling that dialog or whatever gets opened a thing.
Now there are two ways I know of to deal with this,
you create an invisible overlay where only the element you opened
has bigger zindex, and you close your thing by clicking on the
overlay
click event on the document, and you check upon clicking whether you clicked on your thing or outside it, in which case you close your thing.
In either case I'd ideally like to use ng-class to add/remove class that would show/hide the thing.
Now how would I do this with angular, so it could be used on any component(thing) I might add. It's not like I have single modal, I might have quite a few different components, with different html structure, positioning and stuff.
Which approach would be better, document event, overlay or something completely else?
Since angular doesn't really have any reference to dom, document approach could be a problem, right, since I can't quite check whether I'm clicking on the thing element? Unless I'd give every thing the same class..
Overlay approach on the other hand doesn't require any reference to dom elements.
Both approaches would need a unique var at rootScope for that ng-class to work.. which bring the question whether to use ng-class or something custom..
Anyway just throwing my ideas out there, maybe I'm thinking about it wrong from the beginning, has anyone dealt with this before?
The way I've tackled things like this before is using inheritedData to communicate to the click handler whether it's in or out of the thing:
In the custom directive for the thing, add a data variable to the element, using jqLite data, say element.data('myThing',true) . If you want to distinguish between multiple instances of the the thing, you might need to use some uniquely generated key.
In the same custom directive, in a click event handler on document.body, you can check angular.element(event.target).inheritedData('myThing')
An example directive that uses this technique is below
app.directive('thing', function($document,$window) {
return {
restrict: 'E',
template: '<div><span>Inner thing</span></div>',
replace: true,
link: function(scope,element) {
element.data('thing',true);
angular.element($document[0].body).on('click',function(e) {
var inThing = angular.element(e.target).inheritedData('thing');
if (inThing) {
$window.alert('in');
} else {
$window.alert('out');
}
})
}
}
});
and can be seen in this Plunker http://plnkr.co/edit/bRDLcLoesM7Z0BIxKxYu?p=preview
What is the proper way to dynamically add or remove directive from compiled and linked element?
I have a page that has bunch of inputs there (the list is pretty long, so i want to come up with a general solution). What i want to do is to disable all the inputs if specific flag set. I can do this by using jQuery's element.prop('disabled', true).
The problem of such approach is that if any of inputs have ng-disabled or ng-enabled directives attached, then on any their expression modification they will override previously set 'disabled' property. But I want them to not override my global flag.
I came up with the solution to add another bunch of watchers for ng-disabled or ng-enabled expression, but it seems to be not the best approach.
What I want to do, is to remove most of directives attached to the element and set appropriate attributes myself. But if I recompile and relink the element, and then replace it in the document, then I will get a memory leak, as the old element will be de-attached from the DOM document tree, and will remain in memory. I cannot destroy element's scope either, because those elements basically use whole page's main scope.
You can try something like
<div ng-show="someBoolean" >Some text or nested element</div>
or instead of "someBoolean" you can attach a function that resolves to a boolean. To set your boolean you could attach a ng-click to your input that updates your model/boolean value
<button type="button" ng-click="setBoolean()">Some text or nested element </button>
Because of angulars two way data binding the ng-show will be updated upon completion of the next digest cycle
My goal is the following: create a listener that will be bound to a div and it will fire up if there are no children left in that div.
I keep seeing how to bind a listener to say onClick etc.. but I cannot seem to find anyone that deals with actual states of the elements (empty, at least one child, etc... ). I have not started coding anything yet because I am not sure what kind of approach I need to take, since I am pretty new to JavaScript development. I am not necessarily looking for an answer with code in it but more of an advise on what approach to take.
One of the approaches that I was thinking of was to simply have a function call every single time I make a change to the div such as deleting a child but that seems too trivial. I want to create some kind of automation in that process of checking for no children.
jQuery has remove event fired when element is removed
$(el).on("remove", function () {
alert("Element was removed");
});
You could use live (if they are appended dynamically to parent container) or on (if statically) method to bind this event to child nodes of particular container and on every remove event check if parent container has any child nodes. If not then do some actions.
You cannot assign a listener to a div element to check if the element has no children(done automatically without knowing which function is removing the children, but only knows that there was a child removed). Granted you could do a function to check every so many seconds but that is not what I wanted. Anyways, where I remove the children, I simply added a function that checks if the parent is left with no children and it handles it there.
Is it possible and how can I listen for changes through the entire DOM tree with jQuery?
My specific issue: I have a 'tooltip' function that displays the contents of the title attribute in a stylish way when you do a hover on any html element. When you do a hover, however, by standard the browser renders the title in its own box. I would like to supress that. So what I've thought of is to move the contents of the title attribute to a custom (HTML5) data-title attribute the first time the page is loaded, and then my tooltip function will work with data-title.
The problem is that later on I might add / remove / change the HTML dynamically, so I need to 'rebind' those elements - change those title attrs again. It would be nice if there was an event listener that would listen for such changes for me and rebind the elements automatically.
My best guess is that you want to listen to DOM mutation events.
You can do that by DOM mutation event as any normal javascript event such as a mouse click.
Refer to this : W3 MutationEvent
Example:
$("element-root").bind("DOMSubtreeModified", "CustomHandler");
[edited in reply to research by member Tony]
So, without additional code, this is a bit of a blind shot, but it seems to me there are two things to think about here: 1. the default browser tooltip behaviour; 2. a potentially updated DOM and the ability for your custom tooltips to continue functioning.
Regarding #1: when you bind your custom event to the element, you can use event.preventDefault() so that the tooltips don't appear. This doesn't work properly. So, the workaround to keep using the "title" attribute is to grab the value, push it into the data object (the $.data() function), and then null the title with an empty string (removeAttr is inconsistent). Then on mouseleave, you grab the value out of the data object and push it back into the title. This idea comes from here: How to disable tooltip in the browser with jQuery?
Regarding #2: instead of re-binding on DOM change, you just need to bind once to a listener element that is never expected to be destroyed. Usually this is a container element of some sort, but it can even be document (approximating .live() which is now deprecated) if you really need an all-encompassing container. Here's a sample that uses some fake markup of my own devising:
var container = $('.section');
container.on('mouseenter', 'a', function() {
var $this = $(this);
var theTitle = $this.attr('title');
$this.attr('title', '');
$('#notatooltip').html(theTitle);
$.data(this, 'title', theTitle);
});
container.on('mouseleave', 'a', function() {
$('#notatooltip').html('');
var $this = $(this);
var storedTitle = $.data(this, 'title');
$this.attr('title', storedTitle);
});
My unrealistic markup (just for this example) is here:
<div class="section">
Hover this foo!
<div id="notatooltip"></div>
</div>
And a fiddle is here: http://jsfiddle.net/GVDqn/
Or with some sanity checks: http://jsfiddle.net/GVDqn/1/
There's probably a more optimal way to do this (I honestly didn't research if you could bind two separate functions for two separate events with one selector) but it'll do the trick.
You shouldn't need to re-bind based on DOM change, the delegated listener will automatically handle it. And you should be able to prevent default tooltip functionality just by preventing it.
You need to look at this here: http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroupings-mutationevents
As noted by Greg Pettit, you should be using the on() function on the element.
What this does is allows you to bind a selector to an event, then jQuery will add this event handler when the objects returned by the selector are available.
If you wanted a function to fire on a mouse over event and you wanted it to fire on all elements with the class of *field_title* you would do this:
$('.field_title').bind('mouseenter', function() { doSomething(); });
This will trigger on the over mouse over event on any objects that have the class of *field_title* and execute the function doSomething().
Hope that makes sense :)