<html ng-app="dragAndDrop">

<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>

    <link href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">

    <style type="text/css">
        * {
            font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif;
        }

        section {
            border: 1px solid gray;
            border-radius: 1px;
            margin-bottom: 10px;
        }

        ul {
            padding-left: 10px;
        }

        li {
            display: inline-block;
            width: 160px;
            vertical-align: top;
            margin: 5px;
            cursor: pointer;
            box-shadow: 5px 5px 5px 0px #656565;
        }

        li div {
            display: table-cell;
            vertical-align: top;

        }

        .dragdrop-item-controller {
            position: absolute;
            visibility: hidden
        }
    </style>

    <script type="text/ng-template" id="dragItemTemplate">
        <div style="box-shadow: 5px 5px 10px 0px #656565; ">
            <img src="{{value.data.url}}"/>

            <div style="position: absolute;top:0px;left: 0px;background: rgba(255,255,255,0.8);width: 100%">
                {{value.data.title}}
            </div>
        </div>
    </script>


    <title></title>
</head>

<body>

<div class="row">
    <section class="col-md-4" ng-controller="DraggableController">
        <h3>Drag section</h3>
        <ul>
            <li ng-repeat="item in itemsDrag" drag drop-callback="endDrag($index)" drag-include="'dragItemTemplate'"
                drag-model="item">
                <div>
                    <img src="{{item.url}}"/>
                </div>
                <div>
                    <strong>{{item.title}}</strong>:<span>{{item.description}}</span>
                </div>
            </li>
        </ul>
    </section>


    <section ng-controller="Drop1Controller" drop drop-callback="endDrop" drop-mouse-enter="dropClass='bg-info'"
             drop-mouse-leave="dropClass=''" class="col-md-4 {{dropClass}}">
        <h3>Drop section with highlight</h3>
        <ul>
            <li ng-repeat="item in itemsDrop" data-count="{{$index}}">
                <div>
                    <img src="{{item.url}}"/>
                </div>
                <div>
                    <strong>{{item.title}}</strong>:<span>{{item.description}}</span>
                </div>
            </li>
        </ul>
    </section>


    <section ng-controller="Drop2Controller" drop drop-callback="endDrop" drop-mouse-move="mouseMove"
             drop-mouse-leave="mouseLeave" class="col-md-4">
        <h3>Drop section with live insertion</h3>
        <ul id="dropcontainer">
            <li ng-repeat="item in insertDraggedItem(itemsDrop)" data-count="{{$index}}">
                <div>
                    <img src="{{item.url}}"/>
                </div>
                <div>
                    <strong>{{item.title}}</strong>:<span>{{item.description}}</span>
                </div>
            </li>
        </ul>
    </section>
</div>


<div ng-controller="DragAndDropController">
    <div ng-repeat='(key, value) in items' ng-controller="DragAndDropItemController" ng-init="init(value)"
         class="dragdrop-item-controller">
        <div style="width: 100%" ng-include src="value.include">
        </div>
    </div>
</div>


<script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="https://code.angularjs.org/1.2.16/angular.js"></script>
<script src="script.js"></script>
<script type="application/javascript">


    function DraggableController($scope) {
        $scope.itemsDrag = [];

        for (var i = 0; i < 8; i++) {
            $scope.itemsDrag.push({
                title: "Title " + i,
                description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
                url: "http://lorempixel.com/80/80/sports/" + ((i % 8) + 1) 
            });
        }

        $scope.endDrag = function (data) {
            $scope.itemsDrag.splice(data, 1);
        };
    }


    function Drop1Controller($scope) {
        $scope.itemsDrop = [];

        $scope.endDrop = function (data) {
            $scope.itemsDrop.push(angular.copy(data));
            $scope.dropClass='';
        }
    }


    function Drop2Controller($scope, $http) {

        $scope.itemsDrop = [];
        $scope.insertedIndex = -1;
        $scope.inserted = null;


        $scope.endDrop = function (data) {
            if ($scope.insertedIndex >= 0) {
                $scope.itemsDrop.splice($scope.insertedIndex, 0, $scope.inserted);
            } else {
                $scope.itemsDrop.push(angular.copy(data));
            }
            $scope.insertedIndex = -1;
        };


        $scope.insertDraggedItem = function (data) {
            var cloned = data.slice(0);
            if ($scope.insertedIndex >= 0) {
                cloned.splice($scope.insertedIndex, 0, $scope.inserted);
            }
            return cloned;
        };


        $scope.mouseLeave = function (evt, data) {
            $scope.insertedIndex = -1;
        };

        $scope.mouseMove = function (evt, data) {
            var $container = angular.element(document.getElementById("dropcontainer"));

            var $ite = angular.element(evt.target);

            while ($ite.length > 0 && $ite.attr("data-count") == null) {
                $ite = $ite.parent()
            }

            if ($ite.length > 0 && $scope.insertedIndex == $ite.attr("data-count")) {
                return;
            }

            if ($scope.itemsDrop.length == 0) {
                $scope.inserted = angular.copy(data.data);
                $scope.insertedIndex = 0;
            } else if ($ite.length > 0) {
                console.log($ite.attr("data-count"));
                $scope.inserted = angular.copy(data.data);
                $scope.insertedIndex = $ite.attr("data-count");
            }

        };

    }
</script>

</body>

</html>
'use strict';

(function (app) {

    var $DESTROY = "$destroy"

    var MOUSEDOWN = "mousedown";
    var MOUSEUP = "mouseup";

    var MOUSEENTER = "mouseenter";
    var MOUSELEAVE = "mouseleave";
    var MOUSEMOVE = "mousemove";

    var TOUCHSTART = "touchstart";
    var TOUCHEND = "touchend";
    var TOUCHMOVE = "touchmove";
    
    var MOUSE_ID = "mouse";

    var TOUCHEND_ANGULAR_EVENT = "touchend";


    /**
     * Controller for dragged item
     */
    app.controller("DragAndDropItemController", ['$scope', '$element', '$document' , function ($scope, $element) {

        $scope.init = function (value) {
            $scope.value = value;
            $scope.value.$element = $element;
        }
    }]);

    /**
     * Controller for all dragged items
     */
    app.controller("DragAndDropController", ['$rootScope' , '$scope', 'dragAndDrop', '$document' , function ($rootScope, $scope, dragAndDrop, $document) {

        //Mouse up : d&d ended, remove the associated data
        //Need to remove a mouseUp not handled which bubbled to document
        var mouseUp = function (evt) {
            if (dragAndDrop.hasData(MOUSE_ID)) {
                $scope.$apply(function () {
                    dragAndDrop.removeData(MOUSE_ID);
                });
            }
        };

        //Dragged item follow the mouse
        var mouseMove = function (evt) {
            if (dragAndDrop.hasData(MOUSE_ID)) {
                dragAndDrop.getData(MOUSE_ID).$element.css({
                    top: evt.pageY + 5,
                    left: evt.pageX + 5,
                    visibility: "visible"
                });

            }
        };

        //Touched items follow the fingers
        var touchMove = function (evt) {
            for (var ite = 0; ite < evt.targetTouches.length; ite++) {
                var currentTouch = evt.targetTouches[ite];
                if (dragAndDrop.hasData(currentTouch.identifier)) {
                    dragAndDrop.getData(currentTouch.identifier).$element.css({
                        top: currentTouch.pageY + 5,
                        left: currentTouch.pageX + 5,
                        visibility: "visible"
                    });
                    evt.preventDefault();
                }
            }
        };


        //Centralized touchend event, dispatch Touch to all drop directives
        //Must implement centralized touch end because the event only occurs on the element that initiate the touch start (not the final element)
        var touchEnd = function (evt) {
            $scope.$apply(function () {
                for (var ite = 0; ite < evt.changedTouches.length; ite++) {
                    var currentTouch = evt.changedTouches[ite];
                    if (dragAndDrop.hasData(currentTouch.identifier)) {
                        $rootScope.$emit(TOUCHEND_ANGULAR_EVENT, currentTouch);
                        //once emited, remove data
                        dragAndDrop.removeData(currentTouch.identifier)
                    }
                }
            });
        };

        $document.bind(MOUSEMOVE, mouseMove);
        $document.bind(TOUCHMOVE, touchMove);
        $document.bind(MOUSEUP, mouseUp);
        $document.bind(TOUCHEND, touchEnd);

        $scope.$on($DESTROY, function () {
            $document.unbind(MOUSEMOVE, mouseMove);
            $document.unbind(TOUCHMOVE, touchMove);
            $document.unbind(MOUSEUP, mouseUp);
            $document.unbind(TOUCHEND, touchEnd);
        });

        $scope.hasItems = function() {
            return $scope.items.length > 0;
        };

        $scope.items = dragAndDrop.datas;
    }]);


    /**
     * Drag&Drop service, holds dragged item data
     */
    app.factory('dragAndDrop', function () {

        var Service = function () {
            this.counter = 0;
            this.datas = {};
        };


        Service.prototype.setData = function (id, data, success, include, width, height) {
            this.datas[id] = {data: data, include: include, width: width, height: height, callback: success};
        };

        Service.prototype.getData = function (id) {
            return this.datas[id];
        };

        Service.prototype.removeData = function (id) {
            if (this.datas[id]) {
                delete this.datas[id];
            }
        };

        Service.prototype.hasData = function (id) {
            return this.datas[id] != null;
        };

        return new Service();

    });


    app.directive("drag", ["$rootScope", "dragAndDrop", "$controller", function ($rootScope, dragAndDrop) {

        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                var dragInclude = scope.$eval(attrs.dragInclude);

                var success = function () {
                    if (attrs.dropCallback) {
                        var dropCallback = scope.$eval(attrs.dropCallback);
                        if (angular.isFunction(dropCallback)) {
                            dropCallback(dragAndDrop.getData(id));
                        }
                    }
                };

                var mouseDown = function (evt) {

                    evt.preventDefault();
                    scope.$apply(function () {
                        if (attrs.dragModel) {
                            dragAndDrop.setData(MOUSE_ID, scope.$eval(attrs.dragModel), success, dragInclude, element[0].offsetWidth, element[0].offsetHeight);
                        }
                    });
                };

                var touchStart = function (evt) {
                    evt.preventDefault();
                    scope.$apply(function () {
                        for (var ite = 0; ite < evt.targetTouches.length; ite++) {
                            var currentTouch = evt.targetTouches[ite];
                            if (attrs.dragModel) {
                                dragAndDrop.setData(currentTouch.identifier, scope.$eval(attrs.dragModel), success, dragInclude, element[0].offsetWidth, element[0].offsetHeight);
                            }
                        }
                    });
                };


                element.bind(MOUSEDOWN, mouseDown);
                element.bind(TOUCHSTART, touchStart);

                scope.$on($DESTROY, function () {
                    element.unbind(MOUSEDOWN, mouseDown);
                    element.unbind(TOUCHSTART, touchStart);
                });

            }
        }
    }]);


    app.directive("drop", ['$rootScope', "dragAndDrop", "$document", function ($rootScope, dragAndDrop, $document) {

        return {
            restrict: 'A',
            link: function (scope, $element, attrs) {

                function doDrop(identifier, target) {
                    if (attrs.dropCallback) {
                        var result = scope.$eval(attrs.dropCallback);
                        if (angular.isFunction(result)) {
                            result(dragAndDrop.getData(identifier).data);
                        }
                    }
                    dragAndDrop.getData(identifier).callback();
                    dragAndDrop.removeData(identifier);
                }

                var mouseUp = function (evt) {
                    if (!dragAndDrop.hasData(MOUSE_ID)) {
                        return;
                    }
                    evt.preventDefault();
                    scope.$apply(function () {
                        doDrop(MOUSE_ID);
                    });
                };

                var touchEnd = function (s, currentTouch) {
                    var touchEndElement = $document[0].elementFromPoint(currentTouch.pageX, currentTouch.pageY);
                    if ($element[0].compareDocumentPosition(touchEndElement) & Node.DOCUMENT_POSITION_CONTAINED_BY) {
                        doDrop(currentTouch.identifier);
                    }
                };


                var doCallBack = function (cb) {
                    return function (evt) {
                        if (dragAndDrop.hasData(MOUSE_ID)) {
                            scope.$apply(function () {
                                var result = scope.$eval(cb);
                                if (angular.isFunction(result)) {
                                    result(evt, dragAndDrop.getData(MOUSE_ID));
                                }
                            });
                        }
                    }
                };


                $rootScope.$on(TOUCHEND_ANGULAR_EVENT, touchEnd);


                //A mouseup occurs on the target element
                $element.bind(MOUSEUP, mouseUp);
                scope.$on($DESTROY, function () {
                    $element.unbind(MOUSEUP, mouseUp);
                });


                //Mouse enter/leave/move an element
                if (attrs.dropMouseEnter) {
                    var mouseEnter = doCallBack(attrs.dropMouseEnter);
                    $element.bind(MOUSEENTER, mouseEnter);
                    scope.$on($DESTROY, function () {
                        $element.unbind(MOUSEENTER, mouseEnter);
                    });
                }

                if (attrs.dropMouseLeave) {
                    var mouseLeave = doCallBack(attrs.dropMouseLeave);
                    $element.bind(MOUSELEAVE, mouseLeave);
                    scope.$on($DESTROY, function () {
                        $element.unbind(MOUSELEAVE, mouseLeave);
                    });
                }

                if (attrs.dropMouseMove) {
                    var mouseMove = doCallBack(attrs.dropMouseMove);
                    $element.bind(MOUSEMOVE, mouseMove);
                    scope.$on($DESTROY, function () {
                        $element.unbind(MOUSEMOVE, mouseMove);
                    });
                }
            }
        }
    }]);


}(angular.module('dragAndDrop', [])));
See https://github.com/nithril/angular-draganddrop