Loupax
Loupax

Reputation: 4924

How can I execute code after $digest is complete?

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?

Upvotes: 1

Views: 2518

Answers (2)

Valentyn Shybanov
Valentyn Shybanov

Reputation: 19401

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?

Upvotes: 3

Valentyn Shybanov
Valentyn Shybanov

Reputation: 19401

You can try to use Scope.evalAsync Scope.evalAsync:

it will execute after the function that scheduled the evaluation (preferably before DOM rendering).

Upvotes: 1

Related Questions