I've created a directive for Angular to know when a ng-repeat is finished. I've found the most common solution is something like is used here - Calling a function when ng-repeat has finished
But, Why the directive is just triggered when an element is added but not when is removed?
Here - ng-repeat list in Angular is not updated when a model element is spliced from the model array I've seen some suggestion about making a $scope.$apply, but is still not working because it says '$apply already in progress'
I've created here - a Plunker where you can reproduce it.
Thanks a lot!!!
The reason why the directive works when adding an item to the collection is that the link function is called when each repeated element is added to the dom. This works great when the collection is rendered for the first time and when an item is added to the end of the collection. It does not work when an item is added elsewhere in the collection e.g. at the beginning, as the link function is called for the new element but $last will be false.
When an item is removed from the collection no directive is created and hence no link function is called.
To get what you want you can create a watch on the collection and perform whatever you want to do in a $timeout so that it's done after the render:
$scope.$watchCollection('ta', function(){
$timeout( function(){
// the collection has rendered so do all my whizzy stuff
});
});
Most likely it is because ng-repeat doesn't iterate over the whole array when an item is spliced. It just removes that item from the array and removes the associated DOM node.
I don't know what exactly you are trying to achieve, but if you need to react to changes in the ng-repeat list, why don't you just $watch the array instead?
Related
Is there an easy way to remove the contents of a div in Angular,
I have <div class="row"> inside of which I have an ng-repeat that generates several widgets. Now if an action takes place from the parent scope I need to empty that div and call the function that does the ng-repeat again.
Without code it's kind of hard to answer this, but if the ng-repeat is only thing in your div, if you update the array/object bound to the ng-repeat it will automatically update the contents of the div because the ng-repeat will re-evaluate. You don't have to clear it out. Now, if you have other content in the div, this solution won't work.
Angular watches the variables on its own and adds / deletes / updates the ng-repeat on it's own when the array it lists gets updated.
So there is no need to delete it all first, you can just update the object / widget you'd like OR replace the contents of the array it lists with a new array.
May be you are updating data outside of AngularJS.
You can try scope.$apply or an easier way is to wrap the function in an $timeout without delay:
$timeout(function(){ // Update your data });
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");
});
What I want to do is push an object to an array which have angularjs binding to html, then do a click function on the new created element.
the code is like this
$scope.fruits = {"orange", "plum", "cherry"};
$scope.add = function() {
$scope.fruits.push("apple");
//this would add an element to the html, then I want to click on the element
$('#fruits').children.eq(-1).click();//click the last element (which has just been created)
}
the html is like:
<div id="fruits">
<div ng-repeat="fruit in fruits"></div>
</div>
The problem is, when an object is pushed to the model, angular would do something like $scope.$apply() to dynamically update the html.
If I click the last element with .children().eq(-1).click(), it won't wait after the new element been created, what it actually clicks is the "cherry" div.
I tried to call $scope.$apply() before click(), it works, but I got this error message:
$apply already in progress
which I believe is not a good practise, and $apply() should not be called in this way.
When you add to the $scope you are inside a digest loop and the DOM won't be compiled and refreshed until those loops complete - basically in your $scope push you are changing the array but at that moment the rendered DOM still has the previous items. Once it exits the loop, the watchers from the repeater will fire and render the new DOM element, but you've already left your controller.
The solution is really simple. You just need to queue your update for the moment the current digest loop ends. You can do that using setTimeout with no delay. this will put your function on the queue and call it when the current processing is done:
setTimeout(function() {
$('#fruits').children().eq(-1).click();
}, 0);
Here is a working fiddle: http://jsfiddle.net/jeremylikness/bWeQL/
(I strongly encourage you to consider writing a directive for this to avoid manipulating the DOM directly from within your controller).
I am having a sortable list that gets populated by data in my Angular Controller. This list also contains an extra element containing some controls that can also be dragged
What I want do do, is make sure that the extra element stays in place after the $digest cycle is run, and I made a directive just for that case.
App.directive('ngIgnore', ['$timeout',function($timeout){
return {
link: function(scope, element){
scope.$watch(function(){
// Keep track of the original position of the element...
var el = element[0];
var siblings = Array.prototype.slice.call(el.parentNode.children);
var parent = el.parentNode;
var index = siblings.indexOf(el);
$timeout(function(){
// After the digest is complete, place it to it's previous position if it exists
// Otherwise angular places it to it's original position
var item;
if(index in parent.children)
item = parent.children[index];
if(!!item){
parent.insertBefore(el, item);
}
});
});
}
}
}]);
It worked, but not as I wanted it to... As you can see in this example shuffling the list does not move the ignored element, but the problem is that the $watch function gets executed infinitely since the $timeout triggers another $digest cycle... I tried changing the $timeout with setTimeout but no luck...
Is there any Angulary way to run code right after $digest is executed? Or is my entire logic wrong here?
Another (highly recommended) solution is to stop thinking in DOM manipulations.
Common problem with developers who start writing AngularJS code is tend to do DOM manipulations as the result of some event. Its common in jQuery:
$('#someid').click(function() { this.toggleClass('clicked') })
In AngularJS you should design your View to visualize your Model state (that should be in $scope). So
<div ng-click="clicked = !clicked" ng-class="{clicked: clicked}">I was clicked</div>
Same logic should be applied when designing components. In a HTML code you should put all visual logic - hide some elements using ng-show/ng-if, ng-switch. Add/remove classes using ng-class etc. So you define all possible model states.
Then by just changing model state you will get your view automatically updated reflecting current model state.
Same goes for repeated elements: you can repeat on some collection and then, depending on what element is present, you define how it would look. Keep in mind, that in ng-repeat each element will have own child (so mostly independed) Scope, so you can define some per-element manipulations.
See also this great answer: "Thinking in AngularJS" if I have a jQuery background?
You can try to use Scope.evalAsync Scope.evalAsync:
it will execute after the function that scheduled the evaluation
(preferably before DOM rendering).
I'm having some AngularJS trouble with firing off a directive when ng-repeat has completed an update. I have three named arrays, switched to via three links, allowing the selected array to be displayed by the ng-repeat. I'd like to fire some code off when it's finished as I'm planning to set some element attributes for D3 to use.
I've tried checking scope.$last in my directive, but this isn't called at the end of every ng-repeat process. If some of the data remains the same, it may not set scope.$last to true.
http://plnkr.co/edit/hwmOlI6YrgS4H1C1h7k2?p=preview
So, what's the best way to trigger code in a directive when ng-repeat has finished?
Here is a solution for you. Note that the last triggers each time now.
http://plnkr.co/edit/xV1quqzorzxliS2shrP4?p=preview
You just need to $watch the $last variable and it will work fine. This helps in situations where the scope is not created but just updated with new values. Your directive gets created once and if one of the repeated variable just changes values ng-repeat optimizes and just updates values ( as opposed to removing all the values and re-creating the new ones. ). In this scenario, the $scope.$last will be an updated variable and not something that gets "created". So, you will need to $watch it.