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
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.
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)
}
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>
View in codepen
Another example of the solution. Will look like the following
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:
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;
}
Source: Stackoverflow.com