Skip to content
jQuery UI Sortable for AngularJS
JavaScript Other

UI.Sortable directive

Build Status Coverage Status debugInfoEnabled(false) Ready Badge Join the chat at

This directive allows you to sort an array with drag & drop.


  • JQuery
  • JQueryUI 1.9+
  • AngularJS v1.2+

Single minified cdn link ~245kB and example with JQuery v1.x, required parts of JQueryUI v1.10, AngularJS v1.2 & latest angular-ui-sortable.



  • Install with Bower bower install -S angular-ui-sortable
  • Install with npm npm install -S angular-ui-sortable
  • Download one of the Releases or the latest Master branch


Load the script file: sortable.js in your application:

<script type="text/javascript" src="modules/directives/sortable/src/sortable.js"></script>

Add the sortable module as a dependency to your application module:

var myAppModule = angular.module('MyApp', ['ui.sortable'])

Apply the directive to your form elements:

<ul ui-sortable ng-model="items">
  <li ng-repeat="item in items">{{ item }}</li>

Developing Notes:

  • ng-model is required, so that the directive knows which model to update.
  • ui-sortable element should only contain one ng-repeat and not any other elements (above or below).
    Otherwise the index matching of the generated DOM elements and the ng-model's items will break.
    In other words: The items of ng-model must match the indexes of the generated DOM elements.
  • Filters that manipulate the model (like filter, orderBy, limitTo,...) should be applied in the controller instead of the ng-repeat (refer to the provided examples).
    This is the preferred way since it:
    • is performance wise better
    • reduces the chance of code duplication
    • is suggested by the angularJS team
    • it does not break the matching of the generated DOM elements and the ng-model's items
  • ui-sortable lists containing many 'types' of items can be implemented by using dynamic template loading with ng-include or a loader directive, to determine how each model item should be rendered. Also take a look at the Tree with dynamic template example.


All the jQueryUI Sortable options can be passed through the directive.
Additionally, the ui argument of the available callbacks gets enriched with some extra properties as specified to the file. Any model changes that happen inside the available callbacks, are applied right after the stop event. We are not wrapping callbacks like start/change/... with $apply, in order to minimize the number of digest loops and avoid possible modifications of the model (eg: by watchers) before the drop takes place.

myAppModule.controller('MyController', function($scope) {
  $scope.items = ["One", "Two", "Three"];

  $scope.sortableOptions = {
    update: function(e, ui) { ... },
    axis: 'x'
<ul ui-sortable="sortableOptions" ng-model="items">
  <li ng-repeat="item in items">{{ item }}</li>

When using event callbacks (start/update/stop...), avoid manipulating DOM elements (especially the one with the ng-repeat attached). The suggested pattern is to use callbacks for emmiting events and altering the scope (inside the 'Angular world').


Update: Issue 7498 was resolved in jquery-ui v1.11.4. Calling angular.element('ui-sortable').sortable('refresh') (use a more appropriate selector in your use case) should make jquery-ui-sortable recognize the position and orientation of the existing and any new items. As a result, since ui-sortable makes a call to sortable('refresh') after the sortable items are created by the repeater, it is not any more necessary to use the ui-floating property if the orientation of your list is not changing dynamically.
TL;DR: If you are using jquery-ui v1.11.4+ and you are not changing the orientation of your list dynamically, then you probably don't need to use ui-floating property.

To have a smooth horizontal-list reordering, jquery.ui.sortable needs to detect the orientation of the list. This detection takes place during the initialization of the plugin (and some of the checks include: whether the first item is floating left/right or if 'axis' parameter is 'x', etc). There is also a known issue about initially empty horizontal lists.

To provide a solution/workaround (till jquery.ui.sortable.refresh() also tests the orientation or a more appropriate method is provided), ui-sortable directive provides a ui-floating option as an extra to the jquery.ui.sortable options.

<ul ui-sortable="{ 'ui-floating': true }" ng-model="items">
  <li ng-repeat="item in items">{{ item }}</li>


$scope.sortableOptions = {
  'ui-floating': true
<ul ui-sortable="sortableOptions" ng-model="items">
  <li ng-repeat="item in items">{{ item }}</li>

ui-floating (default: undefined)
Type: Boolean/String/undefined

  • undefined: Relies on jquery.ui to detect the list's orientation.
  • false: Forces jquery.ui.sortable to detect the list as vertical.
  • true: Forces jquery.ui.sortable to detect the list as horizontal.
  • "auto": Detects on each drag start if the element is floating or not.


Inside the update callback, you can check the item that is dragged and cancel the sorting.

$scope.sortableOptions = {
  update: function(e, ui) {
    if (ui.item.sortable.model == "can't be moved") {


  • update is the appropriate place to cancel a sorting, since it occurs before any model/scope changes but after the DOM position has been updated. So ui.item.scope and the directive's ng-model, are equal to the scope before the drag start.
  • To cancel a sorting between connected lists, cancel should be called inside the update callback of the originating list.

jQueryUI Sortable Event order

Single sortable demo


/* multiple: sort/change/over/out */

update    <= call cancel() here if needed

Connected sortables demo

list A: start
list B: activate
list A: activate

/* both lists multiple: sort/change/over/out */
list A: sort
list A: change
list B: change
list B: over
list A: sort
list B: out
list A: sort

list A: beforeStop
list A: update    <= call cancel() here if needed
list A: remove
list B: receive
list B: update
list B: deactivate
list A: deactivate
list A: stop

For more details about the events check the jQueryUI API documentation.



Reporting Issues

The above pen's are provided as a good starting point to demonstrate issues, proposals and use cases. Feel free to edit any of them for your needs (don't forget to also update the libraries used to your version).


We use Karma and jshint to ensure the quality of the code. The easiest way to run these checks is to use grunt:

npm install -g grunt-cli
npm install && bower install

The karma task will try to open Firefox and Chrome as browser in which to run the tests. Make sure this is available or change the configuration in test\karma.conf.js

Grunt Serve

We have one task to serve them all !

grunt serve

It's equal to run separately:

  • grunt connect:server : giving you a development server at

  • grunt karma:server : giving you a Karma server to run tests (at http://localhost:9876/ by default). You can force a test on this server with grunt karma:unit:run.

  • grunt watch : will automatically test your code and build your demo. You can demo generation with grunt build:gh-pages.

Something went wrong with that request. Please try again.