Using Plupload To Upload Files In AngularJS
Plupload is a very powerful JavaScript library for local file access and file upload. I've been using it for years and absolutely love it. But, I haven't yet done much with it in AngularJS. Sure, I've uploaded files; but, I haven't leveraged much of the Plupload 2.x features in my AngularJS applications. As such, I wanted to start experimenting with some more robust integrations between Plupload and AngularJS.
View this project on my GitHub account.
There's a significant amount of code in this project, so I don't know how much I actually want to explain in this blog post directly. To me, the most interesting part of this experiment is that I used both a primary uploader (a Plupload "Uploader" instance) and a secondary dropzone (a mOxie "FileDrop" instance). Both of these were encapsulated within AngularJS directives and cross-communicated using the native event system provided by the AngularJS scope chain.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>
Using Plupload To Upload Files In AngularJS
</title>
<link rel="stylesheet" type="text/css" href="css/app.css"></link>
</head>
<body ng-app="PluploadApp">
<h1>
Using Plupload To Upload Files In AngularJS
</h1>
<div ng-controller="ImagesController">
<!--
This is the main image uploader.
--
NOTE: The uploader directive exposes a "queue" value that can be used to
render the list of files inside the uploader.
-->
<div bn-image-uploader class="m-uploader">
<div ng-switch="!! queue.length" class="dropzone">
<!-- Active upload file queue. -->
<ul ng-switch-when="true" class="queue">
<li ng-repeat="item in queue track by item.id" class="item">
{{ item.percent }}%
</li>
</ul>
<!-- Non-active upload instructions. -->
<div ng-switch-when="false" class="instructions">
Select Files — or — Drag-n-Drop Files
</div>
</div>
</div>
<!--
This list shows the uploaded images; but, it also serves as a secondary
dropzone into which the user can drop new images in a targeted location.
-->
<div bn-image-list-uploader class="m-images">
<ul class="images">
<li ng-repeat="image in images track by image.id" class="image">
<div class="thumbnail">
<img ng-src="{{ image.url }}" />
</div>
<div class="name">
{{ image.clientFile }}
</div>
<a ng-click="deleteImage( image )" class="delete">×</a>
<!--
This is here to make the insert into the list much easier.
Without these handles, you have to actually do "math" to figure
out where to put the indicator. I've opted for simplicity over
tidy semantics.
-->
<div class="drop-indicator">
<div class="left"><br /></div>
<div class="right"><br /></div>
</div>
</li>
</ul>
</div>
</div>
<!-- Vendor Scripts. -->
<script type="text/javascript" src="vendor/plupload/plupload.full.min.js"></script>
<!-- Dev script for when you need to see the inner worksing of Plupload. -->
<!--
<script type="text/javascript" src="vendor/plupload/moxie.js"></script>
<script type="text/javascript" src="vendor/plupload/plupload.dev.js"></script>
-->
<script type="text/javascript" src="vendor/jquery/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="vendor/angular/angular-1.2.18.min.js"></script>
<!-- Angular Scripts. -->
<script type="text/javascript" src="js/main.js"></script>
<script type="text/javascript" src="js/images/image-uploader-directive.js"></script>
<script type="text/javascript" src="js/images/image-list-uploader-directive.js"></script>
<script type="text/javascript" src="js/images/images-controller.js"></script>
<script type="text/javascript" src="js/images/images-service.js"></script>
<script type="text/javascript" src="js/services/natural-sort-service.js"></script>
<script type="text/javascript" src="js/services/plupload-service.js"></script>
</body>
</html>
The two Plupload-related directives in the above code are:
- bn-image-uploader
- bn-image-list-uploader
The first handles the primary uploader that supports both "drag-n-drop" and "click-to-select" interactions. This is the instance that actually takes care of POSTing the files to the server.
The second directive handles "drag-n-drop" interaction for the list. But, not only does it allow files to be dropped, it allows them to be dropped in a specific area of the list. This latter aspect, the targeted drop, was what took about 90% of the effort of this entier experiment. Overall, this project took about 2 full days to build and the majority of that time was spent trying to figure out how to get the drop-indicators to show in the right place in the list during the drag events.
If you haven't dealt with drag events yet, they are a complete nightmare! Not only are they a quirky in a cross-browser sense, they also fire in counter-intuitive ways that make them dang-near impossible to work with. If you look at my second directive, you'll see that I basically fallback to using a timer that constantly resets itself. This is terribly inefficient; but, after hour and hours of dead-ends and outlier situations, this was the only thing that I could get to work in all the browsers (including IE 10, which now supports drag-n-drop).
As I move forward with these Plupload / AngularJS experiments, I'll keep trying to think about how to best encapsulate things. You might be tempted to simply wrap Plupload in a generic directive; but I'm not sure that it's actually feasible to do that. As the interactions become more granular, and cross-instance communication is required, I think you'd have to start jumping through unnecessarily-complex hoops in an effort to keep Plupload completely back-boxed.
And, the truth is, Plupload is already encapsulated behind the various mOxie components. My directives aren't breaking that encapsulation - they are doing what directives do - piping JavaScript events into the scope.
Hopefully more fun and interesting stuff to come!
Want to use code from this post? Check out the license.
Reader Comments
Good stuff. Good stuff.
@John,
Thanks my man. Not that it's entirely on-topic, that is to say, not entirely off-topic either, I did a follow-up post on generating per-file Amazon S3 "upload policies":
www.bennadel.com/blog/2653-using-beforeupload-to-generate-per-file-amazon-s3-upload-policies-using-plupload.htm
Plupload is pretty bad-ass. A very well executed library.
Really nice and wonderful effors. Love this blog
What is happeneing in the ImagesController and what all libraries should I import to make file uploading work?
Thank you Ben for such a good tutorial. Can you also write a post on how to upload video files in Angular?
I was originally using Dropzone at my job but switched to Plupload for the chunked uploading. Using your example from Github has been extremely useful in determining how to structure my code. And now large file uploading works! Thanks so much. Love your work.