[javascript] File Upload with Angular Material

I'm writing an web app with AngularJS and angular-material. The problem is that there's no built-in component for file input in angular-material. (I feel that file uploading doesn't fit the material design, but I need it in my app)

Do you have a good solution for this problem?

This question is related to javascript html angularjs file-upload angular-material

The answer is


Based on this answer. It took some time for me to make this approach working, so I hope my answer will save someone's time.

DEMO on CodePen

Directive:

angular.module('app').directive('apsUploadFile', apsUploadFile);

function apsUploadFile() {
    var directive = {
        restrict: 'E',
        templateUrl: 'upload.file.template.html',
        link: apsUploadFileLink
    };
    return directive;
}

function apsUploadFileLink(scope, element, attrs) {
    var input = $(element[0].querySelector('#fileInput'));
    var button = $(element[0].querySelector('#uploadButton'));
    var textInput = $(element[0].querySelector('#textInput'));

    if (input.length && button.length && textInput.length) {
        button.click(function (e) {
            input.click();
        });
        textInput.click(function (e) {
            input.click();
        });
    }

    input.on('change', function (e) {
        var files = e.target.files;
        if (files[0]) {
            scope.fileName = files[0].name;
        } else {
            scope.fileName = null;
        }
        scope.$apply();
    });
}

upload.file.template.html

<input id="fileInput" type="file" class="ng-hide">
<md-button id="uploadButton"
           class="md-raised md-primary"
           aria-label="attach_file">
    Choose file
</md-button>
<md-input-container md-no-float>
    <input id="textInput" ng-model="fileName" type="text" placeholder="No file chosen" ng-readonly="true">
</md-input-container>

with Angular material
HTML

<div (click)="uploadFile.click()">
   <button mat-raised-button color="primary">Choose File</button>
   <input #uploadFile (change)="upload($event)" type='file' style="display:none"/> 
</div>

ts

upload(event:Event){
   console.log(event)
}

stackblitz


Adding to all the answers above (which is why I made it a community wiki), it is probably best to mark any input<type="text"> with tabindex="-1", especially if using readonly instead of disabled (and perhaps the <input type="file">, although it should be hidden, it is still in the document, apparently). Labels did not act correctly when using the tab / enter key combinations, but the button did. So if you are copying one of the other solutions on this page, you may want to make those changes.


Nice solution by leocaseiro

<input class="ng-hide" id="input-file-id" multiple type="file" />
<label for="input-file-id" class="md-button md-raised md-primary">Choose Files</label>

enter image description here

View in codepen


Another example of the solution. Will look like the following enter image description here

CodePen link there.

  <choose-file layout="row">
    <input id="fileInput" type="file" class="ng-hide">
    <md-input-container flex class="md-block">
      <input type="text" ng-model="fileName" disabled>
      <div class="hint">Select your file</div>
    </md-input-container>
    <div>
      <md-button id="uploadButton" class="md-fab md-mini">
        <md-icon class="material-icons">attach_file</md-icon>
      </md-button>
    </div>
  </choose-file>   

.directive('chooseFile', function() {
    return {
      link: function (scope, elem, attrs) {
        var button = elem.find('button');
        var input = angular.element(elem[0].querySelector('input#fileInput'));

        button.bind('click', function() {
          input[0].click();
        });

        input.bind('change', function(e) {
          scope.$apply(function() {
            var files = e.target.files;
            if (files[0]) {
              scope.fileName = files[0].name;
            } else {
              scope.fileName = null;
            }
          });
        });
      }
    };
  });

Hope it helps!


For Angular 6+:

HTML:

<input #csvInput hidden="true" type="file" onclick="this.value=null" (change)="csvInputChange($event)" accept=".csv"/>
<button mat-flat-button color="primary" (click)="csvInput.click()">Choose Spreadsheet File (CSV)</button>

Component method:

  csvInputChange(fileInputEvent: any) {
    console.log(fileInputEvent.target.files[0]);
  }

Note: This filters to just allow .csv files.


Another hacked solution, though might be a little cleaner by implementing a Proxy button:

HTML:

<input id="fileInput" type="file">
<md-button class="md-raised" ng-click="upload()">
  <label>AwesomeButtonName</label>
</md-button>

JS:

app.controller('NiceCtrl', function ( $scope) {
  $scope.upload = function () {
    angular.element(document.querySelector('#fileInput')).click();
  };
};

from jameswyse at https://github.com/angular/material/issues/3310

HTML

<input id="fileInput" name="file" type="file" class="ng-hide" multiple>
<md-button id="uploadButton" class="md-raised md-primary"> Choose Files </md-button>

CONTROLLER

    var link = function (scope, element, attrs) {
    const input = element.find('#fileInput');
    const button = element.find('#uploadButton');

    if (input.length && button.length) {
        button.click((e) => input.click());
    }
}

Worked for me.


You can change the style by wrapping the input inside a label and change the input display to none. Then, you can specify the text you want to be displayed inside a span element. Note: here I used bootstrap 4 button style (btn btn-outline-primary). You can use any style you want.

<label class="btn btn-outline-primary">
      <span>Select File</span>
      <input type="file">
</label>

input {
  display: none;
}

html:
    <div class="upload">
        <span>upload image</span>
        <input
          #Image
          type="file"
          (change)="handleFileInput($event.target.files)"
          accept=".jpg,.svg,.png,.jpeg"
        />
        <img
          width="100%"
          height="100%"
          *ngIf="imageUrl"
          [src]="imageUrl"
          class="image"
        />
    </div>

app.component.ts

export class AppComponent {
  options = [{ value: "This is value 1", checked: true }];
  statuses = ["control"];

  // name = "Angular";//
  fileToUpload: any;
  imageUrl: any;
  handleFileInput(file: FileList) {
    this.fileToUpload = file.item(0);

    //Show image preview
    let reader = new FileReader();
    reader.onload = (event: any) => {
      this.imageUrl = event.target.result;
    };
    reader.readAsDataURL(this.fileToUpload);
  }
}

File uploader with AngularJs Material and a mime type validation:

Directive:

function apsUploadFile() {
    var directive = {
        restrict: 'E',
        require:['ngModel', 'apsUploadFile'],
        transclude: true,
        scope: {
            label: '@',
            mimeType: '@',
        },
        templateUrl: '/build/html/aps-file-upload.html',
        controllerAs: 'ctrl',
        controller: function($scope) {
            var self = this;

            this.model = null;

            this.setModel = function(ngModel) {
                this.$error = ngModel.$error;

                ngModel.$render = function() {
                    self.model = ngModel.$viewValue;
                };

                $scope.$watch('ctrl.model', function(newval) {
                    ngModel.$setViewValue(newval);
                });
            };
        },
        link: apsUploadFileLink
    };
    return directive;
}

function apsUploadFileLink(scope, element, attrs, controllers) {

    var ngModelCtrl = controllers[0];
    var apsUploadFile = controllers[1];

    apsUploadFile.inputname = attrs.name;
    apsUploadFile.setModel(ngModelCtrl);

    var reg;
    attrs.$observe('mimeType', function(value) {
        var accept = value.replace(/,/g,'|');
        reg = new RegExp(accept, "i");
        ngModelCtrl.$validate();
    });

    ngModelCtrl.$validators.mimetype = function(modelValue, viewValue) {

        if(modelValue.data == null){
            return apsUploadFile.valid = true;
        }

        if(modelValue.type.match(reg)){
            return apsUploadFile.valid = true;
        }else{
            return apsUploadFile.valid = false;
        }

    };

    var input = $(element[0].querySelector('#fileInput'));
    var button = $(element[0].querySelector('#uploadButton'));
    var textInput = $(element[0].querySelector('#textInput'));

    if (input.length && button.length && textInput.length) {
        button.click(function(e) {
            input.click();
        });
        textInput.click(function(e) {
            input.click();
        });
    }

    input.on('change', function(e) {

        //scope.fileLoaded(e);
        var files = e.target.files;

        if (files[0]) {
            ngModelCtrl.$viewValue.filename = scope.filename = files[0].name;
            ngModelCtrl.$viewValue.type = files[0].type;
            ngModelCtrl.$viewValue.size = files[0].size;

            var fileReader = new FileReader();
            fileReader.onload = function () {
                ngModelCtrl.$viewValue.data = fileReader.result;
                ngModelCtrl.$validate();
            };
            fileReader.readAsDataURL(files[0]);

            ngModelCtrl.$render();
        } else {
            ngModelCtrl.$viewValue = null;
        }

        scope.$apply();
    });

}
app.directive('apsUploadFile', apsUploadFile);

html template:

<input id="fileInput" type="file" name="ctrl.inputname" class="ng-hide">
<md-input-container md-is-error="!ctrl.valid">
    <label>{@{label}@}</label>
    <input id="textInput" ng-model="ctrl.model.filename" type="text" ng-readonly="true">
    <div ng-messages="ctrl.$error" ng-transclude></div>
</md-input-container>
<md-button id="uploadButton" class="md-icon-button md-primary" aria-label="attach_file">
    <md-icon class="material-icons">cloud_upload</md-icon>
</md-button>

Exemple:

<div layout-gt-sm="row">
    <aps-upload-file name="strip" ng-model="cardDesign.strip" label="Strip" mime-type="image/png" class="md-block">
        <div ng-message="mimetype" class="md-input-message-animation ng-scope" style="opacity: 1; margin-top: 0px;">Your image must be PNG.</div>
    </aps-upload-file>
</div>

I've join some informations posted here and the possibility of personalize components with Angular Material and this is my contribution without external libs and feedback with the name of the chosen file into field:

enter image description here

enter image description here

HTML

<mat-form-field class="columns">
    <mat-label *ngIf="selectedFiles; else newFile">{{selectedFiles.item(0).name}}</mat-label>
    <ng-template #newFile>
        <mat-label>Choose file</mat-label>
    </ng-template>
    <input matInput disabled>
    <button mat-icon-button matSuffix (click)="fileInput.click()">
        <mat-icon>attach_file</mat-icon>
    </button>
    <input hidden (change)="selectFile($event)" #fileInput type="file" id="file">
</mat-form-field>

TS

selectFile(event) {
    this.selectedFiles = event.target.files;
}

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to html

Embed ruby within URL : Middleman Blog Please help me convert this script to a simple image slider Generating a list of pages (not posts) without the index file Why there is this "clear" class before footer? Is it possible to change the content HTML5 alert messages? Getting all files in directory with ajax DevTools failed to load SourceMap: Could not load content for chrome-extension How to set width of mat-table column in angular? How to open a link in new tab using angular? ERROR Error: Uncaught (in promise), Cannot match any routes. URL Segment

Examples related to angularjs

AngularJs directive not updating another directive's scope ERROR in Cannot find module 'node-sass' CORS: credentials mode is 'include' CORS error :Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response WebSocket connection failed: Error during WebSocket handshake: Unexpected response code: 400 Print Html template in Angular 2 (ng-print in Angular 2) $http.get(...).success is not a function Angular 1.6.0: "Possibly unhandled rejection" error Find object by its property in array of objects with AngularJS way Error: Cannot invoke an expression whose type lacks a call signature

Examples related to file-upload

bootstrap 4 file input doesn't show the file name How to post a file from a form with Axios File Upload In Angular? How to set the max size of upload file The request was rejected because no multipart boundary was found in springboot Send multipart/form-data files with angular using $http File upload from <input type="file"> How to upload files in asp.net core? REST API - file (ie images) processing - best practices Angular - POST uploaded file

Examples related to angular-material

error TS1086: An accessor cannot be declared in an ambient context in Angular 9 @angular/material/index.d.ts' is not a module How to set width of mat-table column in angular? Confirm password validation in Angular 6 Set default option in mat-select Can't bind to 'dataSource' since it isn't a known property of 'table' Checkbox angular material checked by default How to set the color of an icon in Angular Material? Disable click outside of angular material dialog area to close the dialog (With Angular Version 4.0+) Styling mat-select in Angular Material