1

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?

Loupax
  • 4,728
  • 6
  • 41
  • 68
  • The complexity and entanglement of your logic is a clear indicator that your approach is off course. You're technique is utterly jQuery-centric. You are first creating your HTML node and than relying on heavy and unnecessary jQuery logic to move the node around. Your "ignored" item should be part of the `$scope.list`. The positioning of the ignored element should be done in scope of the containing `list`, in the controller or directive. Once you do that, the only thing left is to let `ngRepeat` do the rendering for you. – Stewie Dec 20 '13 at 22:11
  • Is it possible to use an array with multiple types of objects in ng-repeat? – Loupax Dec 20 '13 at 23:03
  • 1
    Yes. Actually angular-way is that view/html shouldn't know about how your data is constructed. So you should write html as there is all data present, use ng-if/ng-show/ng-switch to put advanced logic like to show/hide something in case if something should be visible/hidden in some case. But not using JavaScript as in jQuery or some other frameworks – Valentyn Shybanov Dec 21 '13 at 15:29
  • This comment gave me +5 levels in angular... If you make this comment an answer I'll accept it @Valentyn Shybanov – Loupax Dec 23 '13 at 15:26

2 Answers2

3

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?

Community
  • 1
  • 1
Valentyn Shybanov
  • 19,331
  • 7
  • 66
  • 59
1

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

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

Valentyn Shybanov
  • 19,331
  • 7
  • 66
  • 59