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).
Related
https://plnkr.co/edit/WzLez5XElbOHRTvXEJFc?p=preview
function DirectiveController($scope, $element)
{
$scope.Array = ["a", "b"];
$scope.me = function() {
console.log("i ran");
};
};
//Directive HTML
<div class="cool picker" ng-repeat="item in Array">
<input ng-value="me()">
{{item}}
</input>
</div>
A function in directive controller is being called 4 times while directive runs only 2 times by ng-repeat.
I created a thine version of my code in Plunker, so suggestions like "get rid of isolated scope won't help".
i couldn't find a clean explanation here which focus only that matter with no massive code involved or Plunker example.
please advise...please make sure ur console is set on plunker]1
What you are seeing is the digest cycle at work. The digest cycle is how Angular’s auto-update magic works – it’s the reason that typing into an input box automatically updates anything that refers to its value.
When the digest cycle runs, it effectively redraws everything that might have changed on the page.
Angular uses some tricks to find everything that might have changed, and the main technique is watchers. These watchers are created automatically when you use directives like ng-if and ng-class, and when you use bindings like {{ yourBindingHere }}.
Each one of those things registers a watcher. When Angular’s digest cycle runs, every watcher is asked to update its state. In the case of ng-class, it will re-run the function bound to it, to see if anything needs to change. This is why your controller function runs multiple times, and it’ll run again each time something changes on the page.
Now since you are using interpolation {{}} it forces Angular to run once, and using a function with ng-value forces it another time.
The best practice is to not use function inside ng-repeat since it always forces the cycle to check for an update.
And at last use one time binding where possible to prevent any watcher to be created.
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 });
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?
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 have a UI component which has a $watch callback on its width (the reason is not relevant for this post).
The problem is that in some cases:
The width is changed from a non angular context ->
There is no $digest cycle ->
My $watch callback is not called.
Eventhough my application is a full angular application there are still cases in which code is executed in non angular context. For example:
JQuery calles window.setTimeout - so even if my code called JQuery from within angular context the timeout callback is called in non angular context and my $watch callback will not be executed afterwards.
By the way, even angular themselves call window.setTimeout in their AnimatorService...
So my question is:
How can I make sure a $digest cycle is always performed after any code is executed? (even when the code is a 3rd party code...)
I thought about overriding the original window.setTimeout method but:
It feels a bit ugly and dangerous.
I'm afraid it won't cover all use cases.
Adding a plunker.
The plunker sample contains:
An element which can be hidden using JQuery fadeOut method.
A button which executes the fadeOut call for hiding the element.
A text showing the element display status (Shown!!! or Hidden!!!). This text is updated by $watching on the element display property.
A button which does nothing but to initiate some angular code so that a $digest cycle is called.
Flow:
Click the Fade Out button -> the element will be hidden but the status text will remain Shown!!!.
You can wait forever now - or:
Click the Do Nothing button -> suddenly the text will change.
Why?
When clicking the Fade Out button JQuery.fadeOut calls the window.setTimeout method. After that my $watch callback is called but the element is still not hidden.
The element is only hidden after the timeout callback is called - but then there is no $digest cycle (and i have no way that i know of to trigger one).
Only on the next time an angular code will run my $watch function will be called again and the status will be updated.
AngularJS provides a special $apply method on the scope object to allow you to execute code from outside the AngularJS framework.
The $apply function will execute your code in the correct context and apply a $digest cycle afterwards so you don't have to deal with that yourself.
To implement it in your code, you can:
// Get element
var $element = $('#yourElement');
// Get scope
var scope = angular.element($element).scope();
// Execute your code
scope.$apply(function(scope){
// Your logic here
// All watchers in the scope will be triggered
});
(The scenario above can change depending on your actual application).
You can read more about the $apply method of the scope object right here: http://docs.angularjs.org/api/ng.$rootScope.Scope
Hope that helps!
Looking at your plunker, you could add a callback on the call to animate to manually trigger an update to the Angular scope once the animation is complete:
Before:
$scope.fadeOut = function() {
animatedElement.fadeOut('slow');
};
var getDisplay = function() {
return animatedElement.css('display');
};
$scope.$watch(getDisplay, function(display) {
console.log('$watch called with display = `' + display + '`');
$scope.display = display === 'none' ? 'Hidden!!!' : 'Shown!!!';
});
After:
$scope.fadeOut = function() {
animatedElement.fadeOut('slow', function() { $scope.$digest(); });
};
This would cause your watch on getDisplay to be called when the animation is complete, by which time it will return the correct value.