0

I'm relatively new to Angularjs and I'm writing an app. The app has an array of objects, each of the objects has a private variable called status. I have two filters, which return a subset of these objects based on their status. As an example, the statuses corresponding to these two filters are 'NEW' and 'OLD' respectively.

Next, I've written a directive that makes the divs formed by these objects, draggable. Basically, what the directive does is receive the object through a two way data binding, and then changes the status of the object by calling the corresponding method.

The problem is, the changes in the status of the object doesn't update the filter instantly. For example, when I drag three of the divs to the other half, their status gets updated, but the filter doesn't.

Here's the directive:

.directive('draggable', function ($document) {
return {
    scope: {
        bill: '=' // the bill object
    },
    restrict: 'A',
    link: function (scope, element, attr) {
        // do something here
        console.log('bill', scope.bill);
        var startX = 0,
            startY = 0,
            x = 0,
            y = 0,
            sourceX = 0,
            sourceY = 0,
            windowWidth = 0;

        element.on('mousedown', function (event) {
            event.preventDefault();
            startX = event.pageX - x;
            startY = event.pageY - y;
            $document.on('mousemove', mousemove);
            $document.on('mouseup', mouseup);
            windowWidth = $(window).width();
            sourceY = event.pageY;
            sourceX = event.pageX;
        });

        function mousemove(event) {
            y = event.pageY;
            x = event.pageX;
            element.css({
                top: y + 'px',
                left: x + 'px'
            });
        }

        function mouseup(event) {
            $document.unbind('mousemove', mousemove);
            $document.unbind('mouseup', mouseup);
            console.log('mouseup', startX, startY, event.screenX, event.screenY);
            var mid = windowWidth / 2;

            if (sourceX < mid && event.pageX >= mid) {
                // yes there was a change of sides
                scope.bill.markCooking(); // change the status to COOKING
                console.log('moved to COOKING', scope.bill.getStatus());
            } else if (sourceX >= mid && event.pageX < mid) {
                scope.bill.enq(); // change the status to NEW
                console.log('moved to ENQ', scope.bill.getStatus());
            }
        }
    }
}})

What am I doing wrong here?

John Slegers
  • 45,213
  • 22
  • 199
  • 169
Ashesh
  • 2,978
  • 4
  • 27
  • 47

1 Answers1

3

Any time that you're in a browser event handler you are effectively outside of Angular's lifecycle. The user's actions have triggered the event, but Angular doesn't know that it needs to check and update its bindings (in this case, the bill status).

Calling scope.$apply() manually triggers Angular's change detection, and will update your bindings:

scope.$apply(function() {
  if (sourceX < mid && event.pageX >= mid) {
    // yes there was a change of sides
    scope.bill.markCooking(); // change the status to COOKING
    console.log('moved to COOKING', scope.bill.getStatus());
  } else if (sourceX >= mid && event.pageX < mid) {
    scope.bill.enq(); // change the status to NEW
    console.log('moved to ENQ', scope.bill.getStatus());
  }
});

You can read more about scopes and their lifecycle here: http://docs.angularjs.org/guide/scope

c0bra
  • 2,982
  • 23
  • 34
  • 1
    _Any time that you're in a browser event handler you are effectively outside of Angular's lifecycle_. That's only true if you don't use Angular's supported ng-* events. – thescientist Mar 10 '14 at 14:24
  • 1
    When in doubt, and you're not seeing the view updated, you can always try to throw in a call to apply() to see if it fixes the problem. Angular will yell at you if you are already in a digest cycle, so you will know if that's the problem or not. It sounds like it is here. – Matt Pileggi Mar 10 '14 at 14:27
  • Works like a charm, and thanks for the resource! Adding as the answer. – Ashesh Mar 10 '14 at 14:28
  • @thescientist absolutely, I should have been more clear. Manually-bound events that don't use Angular directives will require $apply() to update scope. – c0bra Mar 10 '14 at 14:45
  • Another slightly related question: if I have a template that looks like '
    ' inside a directive called bill, why do I get an error with Angular telling me "…Multiple directives [bill, anotherdirective] asking for new/isolated scope on…"?
    – Ashesh Mar 10 '14 at 15:23
  • 1
    @Ashesh depends on how you're creating the directives. An element can only have one isolate scope. Maybe take a look at this answer: http://stackoverflow.com/questions/20609770/nested-e-directives-multiple-directives-asking-for-new-isolated-scope – c0bra Mar 10 '14 at 15:46