AngularJs - Should I destroy a directive instance manually? - angularjs

Consider an application that utilizes ngRepeat to show a list of directive instances.
When an element is removed from the list, should I manually destroy the directive instance or is it safe enough to call splice() on the array that holds the element?
The developer guide is not very helpful here.

If you are in doubt and want to check whether the directive is actually getting destroyed, you could put a watch on $destroy in your directive. For example:
// inside your link function
scope.$on('$destroy', function() {
console.log("destroyed");
});

Related

Dynamically created element not firing click event

$scope.addNew = function(){
$('.thumb-img-gallary').append("<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>");
}
I am calling this function to add element dynamically. But then delThumbImgGallaryPhoto() is not getting called.
you cannot just append an element with a ng-click or any other directive, and expect it to work. it has got to be compiled by angular.
explenation about compilation from angular docs:
For AngularJS, "compilation" means attaching directives to the HTML to make it interactive
compelation happens in one of two cases:
When Angular bootstraps your application, the HTML compiler traverses the DOM matching directives against the DOM elements
when you call the $compile service inside an angular context
so what you need to do, is first to compile it(remeber to inject the $compile service in your controller), and then append it:
$scope.addNew = function(){
var elem = $compile("<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>")($scope);
$('.thumb-img-gallary').append(elem);
}
BTW, remember it is prefable not to have any DOM manipulations done in the controller. angular has directives for that.
You have to compile it with scope
try like this
var item="<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>"
$('.thumb-img-gallary').append(item);
$compile(item)($scope);
angular doesn't know anything about your newly added element. You need to compile your newly added element using $compile. and you should better use directive for this task.
It is a bad habit to access ui elements from controller.
edit: it would be best using ng-repeat for this task. lets say you have a thumb-gallery directive which is repeated using ng-repeat by thumbs array.
when you need to add a new image you only need to add it to your thumbs array.
it is simple and straightforward
Your html would look like
<thumb-gallery ng-repeat="gallery in galleries"></thumb-gallery>
and your js would look like
var gallery = {};
$scope.galleries.add(gallery);

Is there a way to get a scope of a DOM element when debug info is disabled?

I'm writing an directive which need to retrieve a scope of current DOM element. using the non public api angular.element().scope();
It works well until angular 1.3 introduces a new feature $compileProvider.debugInfoEnabled(false); which mainly aims to improve performance to avoid bind data in DOM element. But when debugInfoEnabled() is set to false, angular.element().scope() will return undefined. So I must find another way to get the scope of an DOM element or I have to redesign my code logic.
Is there a way to make this possible?
I just faced a similar problem in our application after compiling our app with $compileProvider.debugInfoEnabled(false);. I needed to later access some of our directive's isolate scope but couldn't use the isolateScope() method. To get around the problem, I created a helper function in a Utils service that looks like this:
this.setElementIsolateScope = function(element, scope) {
element[0].isolateScope = function() {
return scope;
};
};
Then inside any directive where I needed to be able to later access the isolate scope I called this function inside the link() function: Since element is a jqLite object, you need to set the isolateScope() function on element[0]. You should already have the jqLite wrapped element and scope already passed into your link function, which you then just pass to your service method.
Utils.setElementIsolateScope(element, scope);
To then access the isolate scope later, you would get a reference to your element and then do this (assuming child_element is the reference to your element/directive):
var child_iso_scope = _.isFunction(child_element.isolateScope) && child_element.isolateScope();
Depending on how you are getting the reference to your element, you may need to wrap it a jqLite wrapper like this:
child_element = angular.element(child_element);
And then just use the same way as above to get the isolate scope. Hope this helps!

is it necessary to include a destroy method in my directive

I've written a pretty simple directive that adds/removes a css class on an element when the element is clicked.
app.directive('dropdown', function() {
var open = false, element,
callback = function(){
open = !open;
if (open) {
element.addClass('open');
} else {
element.removeClass('open');
}
};
return {
scope: {},
link: function(scope, elem){
element = elem;
elem.bind('click', callback);
scope.$on('$destroy', function(){
elem.unbind('click', callback);
elem.remove();
});
}
};
});
I think that the $destroy method is probably unnecessary. Since I've used the built in jqlite the listener will be destroyed along with the element right? Also is there any benefit to calling elem.remove(). I've seen it in some examples but not sure if I see the need.
Any thoughts appreciated
C
You don't have to remove the element manually for sure. You also don't need to unbind anything from scope because it will be handled by angularjs itsef.
For jquery dom listeners:
In case you are referencing JQuery then angular will use it instead of his internal jqLite implementation. It means that the native jquery remove method will be used for the element removal. And the jquery documentation for remove says:
Similar to .empty(), the .remove() method takes elements out of the
DOM. Use .remove() when you want to remove the element itself, as well
as everything inside it. In addition to the elements themselves, all
bound events and jQuery data associated with the elements are removed.
So i think that you don't need to unbind your listeners.
But I'm not 100% sure about this:)
In your case, you should be fine since the event is bound to the element that gets removed and thus the handler gets destroyed along with the element itself. Now, if your directive binds an event to a parent outside of its own DOM element, then that would need to be removed manually on the $destroy.
However, closure can cause any object to stay alive so that's something you do need to worry about. You could introduce a new function still referencing variable objects in the functions whose scope you are trying to destroy and that prevents GC from doing what you likely want it to. Again, that won't affect your current example, but it's something to always consider.

How to wait till angularjs is done with everything?

Similar to this question, I want to set focus on the last <select> whenever it gets added. As there's a single method doing it, I need no directive and no watch and no events. My function
$scope.addNew = function() {
$scope.items.push({});
$timeout(function() {
$("select").focus();
});
};
works nicely, except when called directly from the controller function definition like
angular.module('myModule').controller('MyCtrl', function($scope, $timeout) {
$scope.items = {};
...
$scope.addNew();
}
It looks like the timeout happens before the DOM gets constructed and $("select") is empty. With a delay of some 100 ms it works again, but this is a bad hack.
Contrary to what's said in the answer to the linked question, timeout doesn't suffice.
So what's a reliable way to wait for angularjs being really done with the DOM and everything?
Update:
It probably doesn't work because of the select to be focused being embedded in directives (including ng-repeat and some own ones) That's why there initially was no DOM element to focus on.
According to the comments, I need a directive. What's unclear is how exactly to do it. I tried and failed and found out a simpler solution.
What I need
I wasn't very explicit with this, so let me clarify.
I'm working with a table where each row contains some editable fields.
In addNew, I want to set focus on the first editable field of the new row.
In my case this happens to be the very last select.
It worked except at the very beginning, when I was adding the very first row from the controller body.
Why I'm opposed to using a directive
To my limited understanding, it's completely backwards:
A directive modifies the look, behavior, or structure of a given element. But there's no element which should be modified. I tried to put a directive on everything from the select itself to the whole body.
It needs to watch something or listen to an event, but I only want to invoke a function manually.
It didn't work (for me and others as the comments to the linked question shows).
I am going to try and influence you to use a directive here, just to perform the behavior.
Here is a fiddle.
Basic premise is adding the behavioral directive to the element inside repeater:
<table>
<tr ng-repeat="item in items">
<td>{{item}}: <input type="text" auto-focus/></td>
</tr>
</table>
Then your directive would put focus on the last added element:
app.directive('autoFocus', function(){
return function link(scope, elem){
elem[0].focus();
}
});
No watchers or events needed unless I am missing something that you require.
Code that manipulates the DOM should go in a directive, but if you switch to a directive and still have reason to wait until Angular is finished updating the scope and the dom, use $scope.$evalAsync:
$scope.$evalAsync( function() {
// This will wait until Angular is done updating the scope
// Do some stuff here
//
});
The solution was very trivial: Instead of calling $scope.addNew(); directly, I put it in $scope.init invoked from <form ng-init="init()">.
According to the documentation
The only appropriate use of ngInit is for aliasing special properties of ngRepeat, as seen in the demo below. Besides this case, you should use controllers rather than ngInit to initialize values on a scope.
this seems to be wrong (or maybe not, as ngRepeat si involved). I'm only using it to postpone the call to $scope.addNew();, where neither timeout nor posting events worked.

Directive link function called multiple time in ngRepeat

i have a strange behavior in my code with custom directive.
The link function is called multiple times but as example if want to initialize a third-part library (example maps) this behavior breaks the lib.
link: function(scope) {
alert("invoked");
}
http://jsfiddle.net/dYs8L/1/
jsfiddle updated with the correct code
http://jsfiddle.net/dYs8L/2/
It's working as designed...ng-repeat essentially creates a new copy the markup with an isolated scope for every item in the collection. If you need to initialize something once, you'll need to do it elsewhere. You can consider putting this resource into a service; that will allow all the elements output by ng-repeat to use the same instance.

Resources