<!DOCTYPE html>
<html>

  <head>
    <link data-require="jqueryui@*" data-semver="1.10.0" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/css/smoothness/jquery-ui-1.10.0.custom.min.css" />
    <script data-require="jquery@*" data-semver="2.0.3" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
    <script data-require="jqueryui@*" data-semver="1.10.0" src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/jquery-ui.js"></script>
    <script data-require="angular.js@1.2.0-rc3-nonmin" data-semver="1.2.0-rc3-nonmin" src="http://code.angularjs.org/1.2.0-rc.3/angular.js"></script>
    <script src="sortable.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body ng-app="myapp" ng-controller="ctrl">
    <h1>Hello Plunker!</h1>
    <ul psi-sortable="" ng-model="list">
      <li ng-repeat="item in list track by $index"> {{item}} </li>
    </ul>
    <hr>
    <div ng-repeat="item in list track by $index" > {{item}} </dov>
  </body>

</html>
// Code goes here
angular.module('myapp', ['psi.sortable']);

function ctrl($scope) {
  setTimeout(function(){
    $scope.$apply(function(){
      $scope.list = ['first', 'second', 'third', 'last']; 
    });
  }, 500);
}
li {
  border: 1px solid;
  cursor: move;
  background: #ccc;
  padding: 5px;
  margin-bottom: 5px;
}
angular.module('psi.sortable', [])
  .value('psiSortableConfig', {
    placeholder: "placeholder",
    opacity: 0.8,
    axis: "y",
    helper: 'clone',
    forcePlaceholderSize: true
  })
  .directive("psiSortable", ['psiSortableConfig', '$log', function(psiSortableConfig, $log) {
    return {
      require: '?ngModel',
      link: function(scope, element, attrs, ngModel) {

        if(!ngModel) {
          $log.error('psiSortable needs a ng-model attribute!', element);
          return;
        }

        var opts = {};
        angular.extend(opts, psiSortableConfig);
        opts.update = update;

        // listen for changes on psiSortable attribute
        scope.$watch(attrs.psiSortable, function(newVal) {
          angular.forEach(newVal, function(value, key) {
            element.sortable('option', key, value);
          });
        }, true);

        // store the sortable index
        scope.$watch(attrs.ngModel+'.length', function() {
          element.children().each(function(i, elem) {
            jQuery(elem).attr('sortable-index', i);
          });
        });

        // jQuery sortable update callback
        function update(event, ui) {
          // get model
          var model = ngModel.$modelValue;
          // remember its length
          var modelLength = model.length;
          // rember html nodes
          var items = [];

          // loop through items in new order
          element.children().each(function(index) {
            var item = jQuery(this);

            // get old item index
            var oldIndex = parseInt(item.attr("sortable-index"), 10);

            // add item to the end of model
            model.push(model[oldIndex]);

            if(item.attr("sortable-index")) {
              // items in original order to restore dom
              items[oldIndex] = item;
              // and remove item from dom
              item.detach();
            }
          });

          model.splice(0, modelLength);

          // restore original dom order, so angular does not get confused
          element.append.apply(element, items);

          // notify angular of the change
          scope.$digest();
        }

        element.sortable(opts);
      }
    };
  }]);