A File Input Directive For AngularJS

Friday, July 5, 2013

Now that we have a FileReader service for AngularJS, we need something that will give us a file to read. The two ways for users to select files are to use <input type=’file’>,  or to drag and drop a file into the browser.

We’ll build a directive for the file input this week, and look at drag and drop next week.

But first, why are we using a directive?

As discussed before, directives are where the DOM and your Angular code can all come together. Directives can manipulate the DOM, listen for events in the DOM, and move data between the  model and the view. For simple directives, most of the work is in the link function of the directive. Inside of link you’ll have access to your associated DOM element and the current scope. The DOM element is wrapped in jQuery lite (unless you are using jQuery, then it will be wrapped with jQuery), so you can wire up events on the element, change its classes, set its text, its HTML, and so on.

In our fileInput directive, the most important DOM operation is to wire up the change event.

var fileInput = function ($parse) {
    return {
        restrict: "EA",
        template: "<input type='file' />",
        replace: true,          
        link: function (scope, element, attrs) {

            var modelGet = $parse(attrs.fileInput);
            var modelSet = modelGet.assign;
            var onChange = $parse(attrs.onChange);

            var updateModel = function () {
                scope.$apply(function () {
                    modelSet(scope, element[0].files[0]);
                    onChange(scope);
                });                    
            };
            
            element.bind('change', updateModel);
        }
    };
};

The rest of the code in the link function is moving a selected file into the model.  This directive assumes there will be a single file. The directive can also fire off an onChange expression set in the HTML. Most of this work is easy because the $parse service in Angular can essentially turn HTML attribute values into executable code.

Using the directive in markup is easy:

<div file-input="file" on-change="readFile()"></div>

<img ng-src="{{imageSrc}}"/>

The associated controller just needs to implement readFile for the code to all work.

var UploadController = function ($scope, fileReader) {
    
    $scope.readFile = function () {            
        fileReader.readAsDataUrl($scope.file, $scope)
                  .then(function(result) {
                        $scope.imageSrc = result;
                    });
    };
};

Comments
gravatar Kevin Hakanson Friday, July 5, 2013
Looking forward to the drag and drop solution for next week. I did something similar, but it was my first AngularJS project (and only a demo), so I'm sure it has room for improvement. Looks like it is time to read this series of posts and update my code. My solution can be seen as an answer on stackoverflow: http://stackoverflow.com/questions/14712463/angularjs-file-drag-and-drop-in-directive
gravatar Mike Friday, August 9, 2013
How would we take this data and submit it up to a ASP.NET web api endpoint? Also, what if we have other data that needs to be submitted with the image file?
gravatar Scott Monday, August 12, 2013
@Mike: It would look something like this: http://odetocode.com/blogs/scott/archive/2013/01/07/uploading-captured-canvas-images.aspx That is, you create a data url (a string) and POST it to the server where it is parsed. You could use the same AJAX call to send multiple pieces of information. Hope that helps.
gravatar JD White Thursday, August 15, 2013
Thank you so much! This is EXACTLY what I needed for an app I was writing. I was attempting to use jQuery-File-Upload -- http://blueimp.github.io/jQuery-File-Upload/angularjs.html -- with the angular bindings, but it was way overkill. This is clean, simple, and easy-to-understand.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!