- Aug 13, 2013
- 38 Comments
- Tutorials
Drag and Drop File Upload with AngularJS
A reusable AngularJS directive leveraging HTML5 Drag and Drop and the FileReader API
Directives allow you to create new HTML elements, attributes, classes, or comments that can transform the behavior of the DOM. A better way of thinking about directives is “a way to teach HTML new tricks.” In this example, you will see how to leverage directives to solve a common drag and drop problem that can be reused in the future.
See it in action
Note Angular directives are complex. This is by no means a comprehensive tutorial. Find more info on this topic here.
Reusability
Almost every web application today in some way shape or form supports drag and drop. In our particular example, our users required the ability to upload files/images via drag and drop. I immediately looked to directives for the solution; knowing that having this reusable component would come in handy in the future.
Diving into the directive
At a high level, the directive does two major things:
First, it binds to the ‘dragover’ and ‘dragenter’ events on the element the directive is being applied. When these events are triggered, the default browser behavior (displaying the image or file) is prevented via event.preventDefault(). Also, the ‘effectAllowed’ property on the ‘dataTransfer’ property of the event is set to ‘copy’. This restricts the type of drag the user can perform on the element.
1 2 3 4 5 6 7 |
processDragOverOrEnter = (event) -> event?.preventDefault() event.dataTransfer.effectAllowed = 'copy' false element.bind 'dragover', processDragOverOrEnter element.bind 'dragenter', processDragOverOrEnter |
Next it binds to the ‘drop’ event on the element the directive is being applied. When the drop event is triggered, the FileReader API is put to use. This API allows us to base64 encode the dropped files making it easy for client/server transfer via REST protocol.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
element.bind 'drop', (event) -> event?.preventDefault() reader = new FileReader() reader.onload = (evt) -> if checkSize(size) and isTypeValid(type) # Thanks to Angular 2-way data binding we can update # the file and fileName on the bound controllers scope scope.$apply -> scope.file = evt.target.result scope.fileName = name if angular.isString scope.fileName file = event.dataTransfer.files[0] name = file.name type = file.type size = file.size reader.readAsDataURL file false |
The entire source code (written in CoffeeScript) for the directive can be found here.
Using the Directive
Using the directive is as simple as specifying ‘file-dropzone’ as an attribute on the element you wish to receive drop events. At a minimum, you will also need to supply a ‘file’ attribute; The base64 encoded file data will be stored in this attribute.
1 |
<div file-dropzone file="ctrlBoundFile"></div> |
The directive can be configured to only process certain mime types. This is accomplished by specifying an an array of valid mime types to the ‘file-dropzone’ attribute. Also, a max file size (in MB) can be supplied to limit the size of the file being uploaded. This is done by providing an attribute ‘max-file-size’
1 |
<div file-dropzone["text/csv"] file="ctrlBoundFile" max-file-size="2"></div> |
This JSFiddle example shows how to only accept drop actions for png, jpeg, and gif images that are less than or equal to 3MB.
In Closing
If you approach directives with a reusable mindset, you’ll find it yields the following:
- The ability to write code and easily share it amongst different projects and code bases.
- Saved time and development effort in the future.