OdeToCode IC Logo

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;
                    });
    };
};