The de-facto standard of sortable libraries is jQuery UI Sortable, but it’s a bit silly to use jQuery and jQuery UI when you’re already using AngularJS. Why should you have to include two other big asset files for one, small functionality?
Here’s an alternative solution using this sortable library instead.
First off, here’s the demo on Plunker.
There are detailed comments throughout the code, but here’s the basic gist of it:
<!-- html / view --> <div ng-app="myApp"> <div ng-controller="SortController" class="container"> <ul id="my-sort" class="sortable" ng-model="people"> <!-- note the "track by" section. this is important so that it doesn't rebuild the list --> <li ng-repeat="person in people track by person.id" data-index="{{$index}}"> {{person.id}} - {{person.name}} </li> </ul> </div> </div>
// controllers.js var myApp = angular.module('myApp',[]); myApp.controller('SortController', ['$scope', '$http', function($scope, $http) { // initialize data. you would normally use a $http.get() here $scope.newSortIndexes = []; $scope.people = [ { id: 3, name: 'john' }, { id: 4, name: 'mary' }, { id: 1, name: 'james' }, { id: 2, name: 'nancy' } ]; // add sortable to <ul> var mySort = document.getElementById("my-sort"); new Sortable(mySort, { onUpdate: function (evt){ // get new sort order based on indexes var newSortIndexes = []; var liElements = mySort.getElementsByTagName("li"); for (var i=0; i<liElements.length; i++) { newSortIndexes.push(liElements[i].getAttribute('data-index')); } // process change. you would normally use a $http.post() here $scope.newSortIndexes = newSortIndexes; $scope.people = getSorted($scope.people, newSortIndexes); $scope.$apply(); } }); }]); // sort by indexes // http://stackoverflow.com/q/4046967/3261678 function getSorted(arr, sortArr) { var result = []; for(var i=0; i<arr.length; i++) { result[i] = arr[sortArr[i]]; } return result; }
// controllers.js
var myApp = angular.module(‘myApp’,[]);
myApp.controller(‘SortController’, [‘$scope’, ‘$http’, function($scope, $http) {
// initialize data. you would normally use a $http.get() here
$scope.newSortIndexes = [];
$scope.people = [
{ id: 3, name: ‘john’, sort: 0 },
{ id: 4, name: ‘mary’, sort: 1 },
{ id: 1, name: ‘james’, sort: 2 },
{ id: 2, name: ‘nancy’, sort: 3 }
];
// add sortable to
var mySort = document.getElementById(“my-sort”);
new Sortable(mySort, {
onUpdate: function (evt){
angular.forEach($scope.people, function(value, key) {
value.sort = evt.item.parentElement.children[key].getAttribute(‘data-index’);
});
$scope.people = $filter(‘orderBy’)($scope.people, ‘sort’);
}
});
}]);
Hello Amnah,
Thanks for sharing your solution! I’ve been trying to implement a sortable image gallery using the same sortable library that you’ve mentioned and I used data attributes as well.
I’ve been wondering though, isn’t there a way to achieve the same using Angular’s data binding??
Thanks,
Pragun
for a longer lists > 10, this becomes buggy. The fix is to move
$scope.people = getSorted($scope.people, newSortIndexes);
line into the $apply function. Then it works like a charm.
Here is how it should look like:
$scope.$apply( function() {
$scope.people = getSorted($scope.people, newSortIndexes);
});
Thanks for your code through – it saved my day 🙂