3

I'm building a simple app with AngularJS. The app make a async AJAX call to the server and the server returns an array like this:

{
    paragraphs: [
        {content: "content one"},
        {content: "cnt two"},
        {content: "random three"},
        {content: "last one yeeaah"}
    ]
}

So I'm setting this content to the StorageService factory via my set method. Everything is fine here.

I'm using ng-repeat to render the results and JQuery UI sortable to be able to change the order of the elements. When an item is swapped my script is calling the StorageService.swap method and the element order in StorageService is updated, BUT ng-repeat isn't rerendering the change, but if I remove/add or change the content it's working. How I can force the angular to rerender the ng-repeat?

= JSFIDDLE =

http://jsfiddle.net/6Jzx4/3/

= Example =

Example

When a swap occurs the ng-repeat should be rerendered, so the IDs are consecutive

= Code =

HTML

<div ng-controller="Test" sortable>
    <div ng-repeat="item in data().paragraphs" class="box slide_content" id="{{$index}}"> 
        {{item.content}}, ID: {{$index}}
    </div>

    <input type="button" ng-click="add()" value="Add">
</div>

JS

var App = angular.module("MyApp", []);

App.controller("Test", function($scope, StorageService) {
    StorageService.set({
        paragraphs: [
            {content: "content one"},
            {content: "cnt two"},
            {content: "random three"},
            {content: "last one yeeaah"}
        ]
    });

    $scope.data = StorageService.get;

    $scope.add = StorageService.add;
});

App.directive("sortable", function(StorageService) {
    return {
        link: function(scope, element, attrs) {
            $(element[0]).sortable({
                cancel: ".disabled",
                items: "> .slide_content:not(.disabled)",
                start: function(e, t) {
                    t.item.data("start_pos", t.item.index());
                },
                stop: function(e, t) {
                    var r = t.item.data("start_pos");
                    if (r != t.item.index()) {
                                    StorageService.sort($(this).sortable("toArray"));
                    }
                }
            });
        }
    };
});

App.factory('StorageService', function() {
    var output = {};

    return {
        set: function(data) {
            angular.copy(data, output);
            return output;
        },
        get: function() {
            return output;
        },
        add: function() {
            output.paragraphs.push({
                content: 'Content'
            });
        },
        sort: function(order) {
            var localOutput = [];

            for (var j in order) {
                var id = parseInt(order[j]);
                localOutput.push(output.paragraphs[id]);
            }

            console.log('new order', localOutput);

            output.paragraphs = localOutput;
            return output;
        }
    };
});
Deepsy
  • 3,769
  • 7
  • 39
  • 71
  • I don't see the relevant code in the jsfiddle...it also should be here in the post itself. I imagine it's because you didn't correctly utilize promises or you somehow ended up needing a `$scope.$apply()`. – m59 Dec 28 '13 at 23:21
  • what is demo supposed to show us? Hard to help if demo doesn't replicate problem listed – charlietfl Dec 28 '13 at 23:30
  • @charlietfl well look the picture below "Example", it's explaining my problem. When a swap occurs ng-repeat isn't rerendered for some reason. See the middle picture ( after swap ), notice the second element has ID:2 and the third has ID:1, it should be visa-versa as showed in the picture 3. – Deepsy Dec 29 '13 at 00:02
  • can't troubleshoot from a picture and no code. We understand what you expect, but without seeing the code can't do much. Can't you update demo to replicate it? – charlietfl Dec 29 '13 at 00:03
  • @charlietfl check out the fiddle. The code is pretty big, so I inserted it there, gonna put it in the post too now. – Deepsy Dec 29 '13 at 00:04
  • if you change angular scope from code outside angular, like your sortable handlers, have to inform angular using `$apply` otherwise it has no way to know to run a digest – charlietfl Dec 29 '13 at 00:19
  • 1
    The proximal issue is the need for `$apply` but then it looks like there are a number of other incompatibilities between jquery sortable and Angular. So I'd recommend looking at [Angular UI sortable](https://github.com/angular-ui/ui-sortable) It'll take some restructuring to get it going but then you should have something much more compatible. – KayakDave Dec 29 '13 at 01:43
  • @KayakDave yeah I switched to angular UI sortable and it worked. Write your comment as an answer, so I can mark it as solved. – Deepsy Dec 29 '13 at 16:39
  • 1
    Great- glad that worked for you – KayakDave Dec 29 '13 at 17:02

1 Answers1

4

Angular doesn't know you've changed the array. Executing your sort inside a scope.$apply() will address that.

Note that I've added a that variable since this changes meaning inside the apply.

var that = this;
scope.$apply(function() {   
   StorageService.sort($(that).sortable("toArray"));
}

But that fix uncovers other problems that appear to be caused by the interaction between the jQuery sortable and Angular (here's a fiddle that shows an attempt at resolving the problems but still has issues). These issues have been solved in Angular UI Sortable. So a good path forward may be to switch to this.

KayakDave
  • 24,636
  • 3
  • 65
  • 68
  • 1
    Thanks for the answer. I found a few problems tho. Go to the fiddle, swap second field with third, press new: http://i.imgur.com/6fgbaRd.png , Another one: Swap last with first, press new, see what happens. – Deepsy Dec 29 '13 at 00:45
  • 1
    Right you'll need to update add to consider position. Perhaps the other functions too- but you get the concept. Here's one way to update add: http://jsfiddle.net/6Jzx4/5/ – KayakDave Dec 29 '13 at 00:54