Beardslapper
Beardslapper

Reputation: 153

Watch not triggered when service variable is updated in Angular

So, basically what I'm doing is loading the data in the view using ng-repeat. When a drag and drop occurs, I'm moving the item from one group to another using a directive. The directive calls the move function in the service and updates the list variable.

My problem is that when the item is moved, the ng-repeat is not updated. I tried using $scope.$watch, but with no luck. The service list variable is being updated, but the watch in not being triggered for some reason. I tried using broadcast, which worked, but I've read that it's a bad practice to use broadcast in the controller as it creates bugs.

What is the best way to update the ng-repeat? Not sure why it isn't working. If you require more details, please let me know.

This is my service

angular.module('Data', [])
    .factory('DataService', DataService);

function DataService() {
    var list = [];
    list.push({
        id: 6,
        title: "First Group",
        items: [
            {
                id: 1,
                title: "This is an item"
            }
        ]
    });
    list.push({
        id: 7,
        title: "Testng",
        items: [
        ]
    });

    return {
        'get': get,
        'move': move,
    };

    function get() {
        return list;
    }

    function move(index, fromItemIndex, toItemIndex) {    
        list[fromItemIndex].items.push({
                id: 5,
                title: "This is an item"
            });
    }
}

This is my controller

function MyController($scope, DataService) {
    var vm = this;

    vm.list = DataService.get();

    $scope.$watch(function() {
        return DataService.get();
    }, function(value) {
        console.log('Wtatching');
        console.log(value);
    }, true);
}

My View

<div ng-repeat="item in vm.list track by $index">
    <div class="group" droppable group="<% $index %>">
        <div class="group-title">
            <% group.title %>
        </div>
        <div class="group-content">
            <div ng-repeat="item in item.items track by $index">
                <div class="group-item">
                    <div class="group-item-title" 
                         draggable 
                         group="<% $parent.$index %>" 
                         item="<% $index %>">
                        <% item.title %>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

EDIT

Here is a plnker version: https://plnkr.co/edit/bQuotNU7oOm92PCgmhcj

When you drag item 1 and drop it onto itself, you will see that the service list is update, but the watch is not triggered.

Upvotes: 2

Views: 81

Answers (1)

Shashank Agrawal
Shashank Agrawal

Reputation: 25797

I think the problem is with your ng-repeat expression. Try removing the track by $index.

If you are using jQuery (or other non-angular callback) for moving items (drag-and-drop), then you need to wrap your move code like this:

function move(index, fromItemIndex, toItemIndex) {
    $timeout(function() {
        list[fromItemIndex].items.push({
            id: 5,
            title: "This is an item"
        });
    });
}

Make sure you inject $timeout in your factory. There is a concept of digest cycle in Angular where Angular regularly update the data within the Angular context.

From the docs:

In the $digest phase the scope examines all of the $watch expressions and compares them with the previous value. This dirty checking is done asynchronously.

If some Angular data modified in a non-angular context like the jQuery drag & drop, the Angular will be unaware of that change until you tell it. So using $scope.$apply() is used to tell Angular that something has changed. But $apply() sometimes fails if digest cycle is already in progress so using some wrapped service of Angular like $timeout is recommended.

The gist:

The $timeout is used to implicitly trigger a digest cycle

More detail here: https://docs.angularjs.org/error/$rootScope/inprog#triggering-events-programmatically

(Consider using UI.Sortable maintained by Angular UI team if you are using jQuery based library.)

Upvotes: 2

Related Questions