Drag and drop image upload directive for Angular.js

While converting an application of mine to Angular.js, I could not come up with a reasonable way to support drag and drop uploads, so I have created my own.

Browser support

As with everything that is enjoyable on the web, this will only work in IE10+, Chrome and Firefox. This is because you will need to use drag and drop, the File API, and if you upload the images to a server, XmlHttpRequest2.

How it works

When you drag a file over the window, an overlay is displayed (“Drop files here to upload”). When you drop the file, the overlay disappears and an event is fired.

This is what the HTML looks like. When an image is dropped, imageDropped() is called on the controller.

<div id="file-drop" imagedrop on-image-drop="imageDropped()">
    Drop an image here to upload
</div>

The code

First, we need to create our markup. This is the bare minimum you need. We use the ID to style the overlay, imagedrop is the name of the directive and on-image-drop is the controller event that gets called when a file is dropped.

<div id="file-drop" imagedrop on-image-drop="imageDropped()">
    Drop an image here to upload
</div>

Second, we need a little CSS for our directive. This has one purpose: hide the overlay until a file is dragged over the window. When an image is dragged, the overlay will cover the entire window.

body.dragOver #file-drop{display:block;} /* During drag */
body #file-drop{z-index:1000;position:fixed;top:0;left:0;width:100%;height:100%;display:none;} /* No drag */

Here is the directive that powers this simple drop uploader. Pay attention to the comments:

angular.module('ui.imagedrop', [])
.directive("imagedrop", function ($parse, $document) {
    return {
        restrict: "A",
        link: function (scope, element, attrs) {
            var onImageDrop = $parse(attrs.onImageDrop);

            //When an item is dragged over the document
            var onDragOver = function (e) {
                e.preventDefault();
                angular.element('body').addClass("dragOver");
            };

            //When the user leaves the window, cancels the drag or drops the item
            var onDragEnd = function (e) {
                e.preventDefault();
                angular.element('body').removeClass("dragOver");
            };

            //When a file is dropped
            var loadFile = function (file) {
                scope.uploadedFile = file;
                scope.$apply(onImageDrop(scope));
            };

            //Dragging begins on the document
            $document.bind("dragover", onDragOver);

            //Dragging ends on the overlay, which takes the whole window
            element.bind("dragleave", onDragEnd)
                   .bind("drop", function (e) {
                       onDragEnd(e);
                       loadFile(e.originalEvent.dataTransfer.files[0]);
                   });
        }
    };
});

Do not forget to include the directive in your module:

//load ui.imagedrop
var app = angular.module('notes',['ui.imagedrop']);

This is how my controller handles the imageDropped() event:

//Drop uploads
$scope.imageDropped = function(){
    //Get the file
    var file = $scope.uploadedFile;

    //Upload the image
    //(Uploader is a service in my application, you will need to create your own)
    Uploader.uploadImage(file).then(
        function(imageUrl){
            //Application-specific stuff...
        }
    );

    //Clear the uploaded file
    $scope.uploadedFile = null;
};

VoilĂ !

I am still getting familiar with Angular.js, so if you have any improvements to suggest, feel free to post them in the comments section.

7 comments on “Drag and drop image upload directive for Angular.js

  1. Thanks for this great help. I needed to change this in the controller : “loadFile(e.dataTransfer.files[0]); /* This is the file */” and it worked perfectly.

    • Reason for this, IMHO is that you did not have jquery loaded before angular.js. I had the same thing. When I ported my POC into the main project I had to revert to the original post. and the only difference beyond (me using typescript ;) is that jquery is loaded before angular and in my spike it wasn’t. Just my 2 cents.

      THANKS for the POST

  2. Thanks for posting this – I would however say that this is a good example of unnecessary use of an extra library (jQuery). You could use the $document service instead of jQuery for that.

  3. Thanks you so much for the detailed explanation of the drag-n-drop using AngularJS.

    I was wondering if you could provide a working demo using CodePen or jsfiddle.

    Tarek

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax