marceljuenemann / angular-drag-and-drop-lists

Angular directives for sorting nested lists using the HTML5 Drag & Drop API
MIT License
2.16k stars 713 forks source link

track by $index fails #414

Closed amitparrikar closed 7 years ago

amitparrikar commented 7 years ago

I am trying to reorder my unordered list.

If I use track by $index inside my ng-repeat, then following things happen: 1) If I drag and reorder a list item from top to down it works! 2) If I drag and reorder a list item from bottom to top it creates a duplicate copy of the item being dragged.

Check the fiddle

I appreciate your help. Thanks :)

letmejustfixthat commented 7 years ago

Can confirm for angular-drag-and-drop-lists v2.1.0. Do you have figured out any work-arround/solution?

alexsk commented 7 years ago

track by doesn't work correctly because the same item exists twice in the array for some short period of time. dnd-moved (where we're removing the item) is called after a new item was added. I had the same problem with tracking items by unique id and got errors in the console.

Here is my workaround: instead of using dnd-moved I moved all logic to dnd-drop callback, doing all removings and insertions by myself in the correct order. My result code looks the following:

$scope.dndDropCallback = function(index, item) {
    var i, l,
        currentIndex = 0,
        list = $scope.list;

    for (i = 0, l = list.length; i < l; i++) {
        if (list[i].id === item.id) {
            currentIndex = i;
            break;
        }
    }

    // drag to the same place
    if (index === currentIndex || index === (currentIndex + 1)) {
        return false;
    }

    // remove the item from the current position
    list.splice(currentIndex, 1);

    // 'fix' the new position index if it was shifted after the removing
    if (currentIndex < index) {
        index--;
    }

    // insert the item into the new position
    list.splice(index, 0, item);
    return true;
};

Reordering in both directions works as expected now. Hope it will help you

foxinatardis commented 7 years ago

Somehow it seems that the item passed to the dnd-moved callback when moving upwards is actually the element above the one the user is grabbing.

fromcouch commented 7 years ago

Any solution for this bug? I have problem with duplicates without track by!

amitparrikar commented 7 years ago

Well if I remember correctly I skipped using "track by $index". If you have not moved much ahead with the time then consider using https://github.com/luckylooke/dragular

fromcouch commented 7 years ago

Maybe I've project finished. I have problem changing route, when I go back problem appears.

amitparrikar commented 7 years ago

@fromcouch i have no idea why you disliked the suggestion, anyways that was a whole hearted suggestion.. Take it or leave it.

letmejustfixthat commented 7 years ago

Did I miss the solution or why is this closed? Not be able to use 'track by' seems to be a dealbreaker for a drag and drop lib imho

foxinatardis commented 7 years ago

Seems unsolved to me. The problem seems to stem from the directive having the wrong index / item when moving an item upwards. You can get around the problem by checking the item passed to the directive via the 'dnd-moved' function.

When an item is moved, search the list for a duplicate item. If there is no duplicate, or the index passed to the directive matches either index of the duplicates you can simply remove via list.splice(index, 1). Otherwise the item has been moved upwards and you need to remove the item via the index of the last duplicate found.

Hope that was clear enough and helps you on your way.

Regards

yuriylianguzov commented 7 years ago

Had the same issue. In my case I was iterating over the array of strings. And then I was getting dupes error if I use track by $index. Seems like iterating over the array of objects solves this issue. So instead of doing this: ctrl.items = [ 'item1', 'item2', 'item3' ] I did this: ctrl.items = [ {label: 'item1', index: 0}, {label: 'item2', index: 1}, {label: 'item3', index: 2}] and then in the view: <li ng-repeat="item in ctrl.items">

letmejustfixthat commented 7 years ago

Seems like iterating over the array of objects solves this issue.

I disagree, technical details (or bugs) may not dictate the data-structure. As long as the apps are tiny this may the a valid option. But, as I wrote before, a drag and drop lib breaking the track by syntax is no option to use. Removing the lib seems to be the best option.

xak2000 commented 6 years ago

The key principle here is to not use dnd-moved callback and use dnd-drop callback instead, manually removing and inserting the item at new place inside one function ($ctrl.onDrop) between dirty-checking cycles, so the ng-repeat directive has no chance to notice the duplicate.

The template:

    <ul dnd-list="$ctrl.list"
          dnd-drop="$ctrl.onDrop(callback(), index)">
      <li ng-repeat="item in $ctrl.list track by item.id"
          dnd-draggable="item"
          dnd-callback="$index">
        {{item.name}}.
      </li>
    </ul>

The controller:

    var $ctrl = this;
    $ctrl.list = [{id: 1, name: 'A'}, {id: 2, name: 'B'}, {id: 3, name: 'C'}];
    $ctrl.onDrop = function (srcIndex, dstIndex) {
      dstIndex = dstIndex > srcIndex ? dstIndex - 1 : dstIndex;
      var dropAllowed = srcIndex !== dstIndex;
      if (dropAllowed) {
        $ctrl.list.splice(dstIndex, 0, $ctrl.list.splice(srcIndex, 1)[0]);
        return true;
      }
      return false;
    };
JohnnyTheTank commented 6 years ago

this issue is still existing