In the past, I've used base64-encoded data URIs to show client-side image previews. However, in a recent revamp of InVision App, Jonathan Rowny started rendering image previews using a relatively new browser technology - Object URLs. Rather than reading the image into memory and converting it into another format (base64), Object URLs allow us to link directly to the file on the user's local system. This has both memory and performance considerations. And, since I've never played with this myself, I wanted to put together a quick little demo.
In the following demo, I've created a component directive that provides a dropzone. When you drop images onto the dropzone, the image previews are rendered in a list. The directive can render the image using either the older base64 data URI approach or the newer Object URL approach. In fact, the difference in access patterns is so minimal, it can be easily encapsulated behind the promise API.
That said, the URL.createObjectURL() method is actually synchronous. Since it's not really doing any processing - just providing a local URL - it can return immediately. The base64 data URI, on the other hand, needs to read the file into memory and then encode it as a string. As such, it is necessarily an asynchronous promise. To keep things uniform, however, I am putting both access patterns behind a promise such that the consuming code can assume async access for both approaches.
Once an Object URL is created using URL.createObjectURL(), it is recommend that you subsequently deallocate the url, when you no longer need it, using URL.revokeObjectURL(). Apparently this is for memory management; however, when using the Chrome DevTools Profiler, I wasn't seeing anything concerning when I ran the demo without explicit deallocation. That said, I'm all for best practices; so, in the following code, I keep track of the URLs as I generate them and then deallocate them in the $destroy event.
With all that said, let's take a look at the code. The first instance of the component directive uses Object URLs (as per the preview-method="url" attribute) and the second instance of the component directive uses base64 data URIs (as per the preview-method="base64" attribute):
As you can see, in either approach, the link() function takes care of managing the dropzone and extracting the image preview URL. Then, regardless of the preview technique, the link() function lets the controller know about the new image to render as part of the view-model. And, when we run this code, you can see what a local Object URL looks like:
If you watch the video, or you play around with demo, you'll likely see that the Object URL approach is faster than the base64 approach. This difference is more pronounced in some browsers (Firefox) and less pronounced in other browsers (Safari). But, the support is pretty good (IE10+).
This is some pretty cool stuff. And, with decent support. Since the difference in workflow between Object URLs and base64 data URIs is insignificant, I don't see any reason to not try to use Object URLs by default and then fallback to base64 data URIs in slightly older versions of IE. It wouldn't even be a lot of code - throw it behind a promise and you're ready to rock!
Nice. Thanks also for drawing my attention to Plupload (www.bennadel.com/blog/2652-using-plupload-to-upload-files-in-angularjs.htm) as I can't imagine how I'd handle that part myself.
Fun to see you componentizing your A1. Migrating it to A2 shouldn't be too hard when the time comes.
Plupload is awesome. I've been using it for years and it's been really great. Not everyone at work loves it; but, we keep using it because anytime someone tries to look at some other library, they find reasons that Plupload has some feature of known quantity that makes it the uploader of choice. One of the things that I love about it is that you can have multiple dropzones that are easy to pipe into the main uploader.
Yea Plupload is helpful, I Just wish Plupload it was managed better and the licensed as MIT so we could add improvements like PUTs to S3 without it taking years. The jumbled monolithic code, the license, and the slow management I think turn off opensource contributions that could make it really great.
I also find the code very hard to follow internally. The way it creates a "Runtime" and then connects and has to disconnect each "Runtime Client" with a runtime, is hard to understand, unless you wrote the code (presumably). This also makes debugging memory leaks pretty hard. You have to *destroy all the things*.
But, still, I do love it :D
Thanks for this explanation. While angular probably doesn't mind, this is a big deal for react, as huge base64-encoded strings thrown around while doing virtual dom diffing doesn't work so fast. So there's another significant win, although that's somewhere else :D
Ah, very cool perspective. Glad to know this has some hidden benefits as well.