<input> file is not updating/wroking properly in AngularJs - angularjs

I am trying to write a directive for <input> type file. This directive will handle file change event and I am trying to return following object as a model.
{ fileName: element[0].files[0].name, fileAsBuffer: e.target.result }
My directive code
"use strict";
(function() {
angular
.module("fileUploadApp")
.directive("customFileChange", customFileChange);
customFileChange.$inject = ["$parse"];
function customFileChange($parse) {
return {
restrict: "A",
link: function(scope, element, attrs) {
var model = $parse(attrs.customFileChange);
var modelSetter = model.assign;
element.bind("change", function() {
scope.$apply(function() {
var reader = new FileReader();
reader.onload = function(e) {
var fileModel = {
fileName: element[0].files[0].name,
fileAsBuffer: e.target.result
};
modelSetter(scope, fileModel);
}
reader.onerror = function(e) {
console.log(e.target.error);
}
reader.readAsArrayBuffer(element[0].files[0]);
});
});
}
};
}
})();
My html
<input type="file" name="name" data-custom-file-change="vm.newDocument.attachment" />
<p>{{vm.newDocument.attachment.fileName}}</p>
Problem:
First time when I upload a file, It is not showing anything in
<p>{{vm.newDocument.attachment.fileName}}</p>
Second time if I upload another file, the It is showing the first
file name in <p>{{vm.newDocument.attachment.fileName}}</p>
JS Bin

You need to trigger the digest cycle from inside the callback function itself.
For example:
reader.onload = function (e) {
var fileModel = { fileName: element[0].files[0].name, fileAsBuffer: e.target.result };
scope.$apply(function () {
modelSetter(scope, fileModel);
});
};
Demo: https://jsbin.com/nicocosige/1/edit?html,js,console,output

Related

Some problems in the deployment of the dropzone in AngularJS 1.4.9

When I try to erase the image in the dropzone there are traces of it, which prevent me from uploading it again, unless I upload and erase another image before. I'm using the 1.4.9 version of AngularJS
//controller
app.controller('AppController',function($http){
var ctrl = this;
ctrl.data = { upload:[] } // <= upload data get pushed here
ctrl.upload= function(){
console.log(ctrl.data);
$http({
method : 'POST',
url : 'upload.php',
data : ctrl.data,
}).success(function(data){
alert(data);
});
}
ctrl.delete= function(){
ctrl.data = { upload:[] };
}
});
//and here the directive who controls the upload an the image base64 conversion
app.directive('dropZone',[
function(){
var config = {
template:'<label class="drop-zone">'+
'<input type="file" accept="jpg,png" />'+
'<div ng-transclude></div>'+ // <= transcluded stuff
'</label>',
transclude:true,
replace: true,
require: '?ngModel',
link: function(scope, element, attributes, ngModel){
var upload = element[0].querySelector('input');
upload.addEventListener('dragover', uploadDragOver, false);
upload.addEventListener('drop', uploadFileSelect, false);
upload.addEventListener('change', uploadFileSelect, false);
config.scope = scope;
config.model = ngModel;
}
}
return config;
// Helper functions
function uploadDragOver(e) { e.stopPropagation(); e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; }
function uploadFileSelect(e) {
console.log(config.scope.app.data.upload);
if (config.scope.app.data.upload.length+1==1) {
/* console.log("Hola"+config.scope.app.data.upload.length); */
e.stopPropagation();
e.preventDefault();
var files = e.dataTransfer ? e.dataTransfer.files: e.target.files;
for (var i = 0, file; file = files[i]; ++i) {
/* console.log(file); */
var reader = new FileReader();
reader.onload = (function(file) {
return function(e) {
// Data handling (just a basic example):
// [object File] produces an empty object on the model
// why we copy the properties to an object containing
// the Filereader base64 data from e.target.result
var data={
data:e.target.result,
dataSize: e.target.result.length
};
for(var p in file){ data[p] = file[p] }
config.scope.$apply(function(){ config.model.$viewValue.push(data) })
}
})(file);
reader.readAsDataURL(file);
}
}else{console.log("Solo puedes subir una foto.")}
}
}
])
What i expect
When you delete the image in the dropzone, you can upload it again. That is the expected result.

How to upload multiple files in angular js and convert it into base 64

I am used single file upload and convert it to base 64 which was working fine.I am trying to upload multiple files and convert each file to base 64 in which i have used the following directive.What happens is all the image is converted into a single base 64
I would like to convert each image file to base 64.so that can anyone help me how to achieve it.
Directive:
"use strict";
(function() {
angular.module("SS.pages").directive("fileModel", fileModel);
/** #ngInject */
function fileModel($parse, $q) {
return {
restrict: "A",
link: function(scope, element, attrs) {
var model = $parse(attrs.fileModel),
modelSetter = model.assign;
element.bind("change", function() {
var files = [];
angular.forEach(element[0].files,function(file) {
files.push(file);
});
scope.$apply(function() {
// var file = element[0].files[0];
angular.forEach(files, function(file) {
getFileBuffer(file).then(function(resp) {
modelSetter(scope, resp);
});
});
});
});
}
};
function getFileBuffer(file) {
var deferred = new $q.defer();
var reader = new FileReader();
reader.onloadend = function(e) {
deferred.resolve(e.target.result);
};
reader.onerror = function(e) {
deferred.reject(e.target.error);
};
reader.readAsDataURL(file);
return deferred.promise;
}
}
})();
Html:
<div class="form-group">
<label translate>.NEW.UPLOAD</label>
<input type="file" class="form-control" name="schemeupload" accept="image/jpeg,application/jpeg" file-model="vm.schemeApply.document" multiple/>
<p class="help-block" translate>.NEW.UPLOADMESSAGE</p>
<span class="help-block error-block basic-block" translate>.NEW.REQUIRED</span>
</div>
It would be wiser to upload the files directly. Converting them to base64 adds 33% extra overhead
app.directive("filesInput", function() {
return {
require: "ngModel",
link: function postLink(scope,elem,attrs,ngModel) {
elem.on("change", function(e) {
var files = elem[0].files;
ngModel.$setViewValue(files);
})
}
}
});
Usage
<input type="file" files-input ng-model="filesArray"
ng-change="uploadMultiple(filesArray)" multiple />
JS
$scope.uploadMultiple = function(filesArray) {
var config = { headers: {'Content-Type': undefined} };
var promises = filesArray.map(function (file) {
return $http.post(url, file, config);
});
$q.all(promises)
.then(function(responseArray) {
console.log("All files uploaded");
}).catch(function(error) {
console.log("ERROR", error);
});
};
Converting large files to base64 has been known to cause problems. See, FileReader readAsDataURL() method not read large pdf file 24 MB.

FileReader() onload function not firing

I know this is a duplicate but none of the answers have helped so far..
I have this directive:
app.directive('fileReader', function () {
return {
scope: {
fileReader: "="
},
link: function (scope, element) {
$(element).on('change', function (changeEvent) {
var files = changeEvent.target.files;
if (files.length) {
var reader = new FileReader();
console.log('reader created');
reader.onload = function (e) {
console.log('onload hit');
};
}
});
}
};
})
And this DOM element linked to the directive:
<input type="file" id="file" file-reader="fileContent"/>
When I select a file for my input, my console prints out reader created, but never prints onload hit. I can not figure out how to properly fire off my onload function. What am I doing wrong here?
### Directive ###
app.directive('fileReader', ['$rootScope', function($rootScope) {
return {
scope: {
fileReader:"="
},
link: function(scope, element) {
$(element).on('change', function(changeEvent) {
var files = changeEvent.target.files;
if (files.length) {
$rootScope.uploadedFileName = files[0].name;
var r = new FileReader();
r.onload = function(e) {
var contents = e.target.result;
scope.$apply(function () {
scope.fileReader = contents;
$rootScope.uploadedFileName = files[0].name;
});
};
r.readAsText(files[0]);
}
});
}
};
}]);
### HTML ###
<input type="file" accept=".csv" id="myFile" file-reader="fileContent"/>

Angularjs: any directive to convert a file XLSX, XLS to Array [object] ou other best practice?

I would like to know the best way to import a Excel file in my HTML, with Angular and read it data. Any ideias?
I did this but from .CSV to array object, it worked perfect with a angular directive. Is there anything to Excel files?
This is the directive:
// CSV -> Angularjs Object
MyApp.directive('fileReader', function () {
return {
scope: {
fileReader: "="
},
link: function (scope, element) {
$(element).on('change', function (changeEvent) {
var files = changeEvent.target.files;
if (files.length) {
var r = new FileReader();
r.onload = function (e) {
var contents = e.target.result;
scope.$apply(function () {
scope.fileReader = contents;
});
};
r.readAsText(files[0]);
}
});
}
};
});
HTML:
<input type="file" file-reader="fileContent" />
ANGULAR:
$scope.fileContent;
This can help : https://github.com/brexis/angular-js-xlsx
But it didn't get my number, just letters or letters+numbers.

ng-model for `<input type="file"/>` (with directive DEMO)

I tried to use ng-model on input tag with type file:
<input type="file" ng-model="vm.uploadme" />
But after selecting a file, in controller, $scope.vm.uploadme is still undefined.
How do I get the selected file in my controller?
I created a workaround with directive:
.directive("fileread", [function () {
return {
scope: {
fileread: "="
},
link: function (scope, element, attributes) {
element.bind("change", function (changeEvent) {
var reader = new FileReader();
reader.onload = function (loadEvent) {
scope.$apply(function () {
scope.fileread = loadEvent.target.result;
});
}
reader.readAsDataURL(changeEvent.target.files[0]);
});
}
}
}]);
And the input tag becomes:
<input type="file" fileread="vm.uploadme" />
Or if just the file definition is needed:
.directive("fileread", [function () {
return {
scope: {
fileread: "="
},
link: function (scope, element, attributes) {
element.bind("change", function (changeEvent) {
scope.$apply(function () {
scope.fileread = changeEvent.target.files[0];
// or all selected files:
// scope.fileread = changeEvent.target.files;
});
});
}
}
}]);
I use this directive:
angular.module('appFilereader', []).directive('appFilereader', function($q) {
var slice = Array.prototype.slice;
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, element, attrs, ngModel) {
if (!ngModel) return;
ngModel.$render = function() {};
element.bind('change', function(e) {
var element = e.target;
$q.all(slice.call(element.files, 0).map(readFile))
.then(function(values) {
if (element.multiple) ngModel.$setViewValue(values);
else ngModel.$setViewValue(values.length ? values[0] : null);
});
function readFile(file) {
var deferred = $q.defer();
var reader = new FileReader();
reader.onload = function(e) {
deferred.resolve(e.target.result);
};
reader.onerror = function(e) {
deferred.reject(e);
};
reader.readAsDataURL(file);
return deferred.promise;
}
}); //change
} //link
}; //return
});
and invoke it like this:
<input type="file" ng-model="editItem._attachments_uri.image" accept="image/*" app-filereader />
The property (editItem.editItem._attachments_uri.image) will be populated with the contents of the file you select as a data-uri (!).
Please do note that this script will not upload anything. It will only populate your model with the contents of your file encoded ad a data-uri (base64).
Check out a working demo here:
http://plnkr.co/CMiHKv2BEidM9SShm9Vv
How to enable <input type="file"> to work with ng-model
Working Demo of Directive that Works with ng-model
The core ng-model directive does not work with <input type="file"> out of the box.
This custom directive enables ng-model and has the added benefit of enabling the ng-change, ng-required, and ng-form directives to work with <input type="file">.
angular.module("app",[]);
angular.module("app").directive("selectNgFiles", function() {
return {
require: "ngModel",
link: function postLink(scope,elem,attrs,ngModel) {
elem.on("change", function(e) {
var files = elem[0].files;
ngModel.$setViewValue(files);
})
}
}
});
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
<h1>AngularJS Input `type=file` Demo</h1>
<input type="file" select-ng-files ng-model="fileArray" multiple>
<code><table ng-show="fileArray.length">
<tr><td>Name</td><td>Date</td><td>Size</td><td>Type</td><tr>
<tr ng-repeat="file in fileArray">
<td>{{file.name}}</td>
<td>{{file.lastModified | date : 'MMMdd,yyyy'}}</td>
<td>{{file.size}}</td>
<td>{{file.type}}</td>
</tr>
</table></code>
</body>
This is an addendum to #endy-tjahjono's solution.
I ended up not being able to get the value of uploadme from the scope. Even though uploadme in the HTML was visibly updated by the directive, I could still not access its value by $scope.uploadme. I was able to set its value from the scope, though. Mysterious, right..?
As it turned out, a child scope was created by the directive, and the child scope had its own uploadme.
The solution was to use an object rather than a primitive to hold the value of uploadme.
In the controller I have:
$scope.uploadme = {};
$scope.uploadme.src = "";
and in the HTML:
<input type="file" fileread="uploadme.src"/>
<input type="text" ng-model="uploadme.src"/>
There are no changes to the directive.
Now, it all works like expected. I can grab the value of uploadme.src from my controller using $scope.uploadme.
I create a directive and registered on bower.
This lib will help you modeling input file, not only return file data but also file dataurl or base 64.
{
"lastModified": 1438583972000,
"lastModifiedDate": "2015-08-03T06:39:32.000Z",
"name": "gitignore_global.txt",
"size": 236,
"type": "text/plain",
"data": "data:text/plain;base64,DQojaWdub3JlIHRodW1ibmFpbHMgY3JlYXRlZCBieSB3aW5kb3dz…xoDQoqLmJhaw0KKi5jYWNoZQ0KKi5pbGsNCioubG9nDQoqLmRsbA0KKi5saWINCiouc2JyDQo="
}
https://github.com/mistralworks/ng-file-model/
This is a slightly modified version that lets you specify the name of the attribute in the scope, just as you would do with ng-model, usage:
<myUpload key="file"></myUpload>
Directive:
.directive('myUpload', function() {
return {
link: function postLink(scope, element, attrs) {
element.find("input").bind("change", function(changeEvent) {
var reader = new FileReader();
reader.onload = function(loadEvent) {
scope.$apply(function() {
scope[attrs.key] = loadEvent.target.result;
});
}
if (typeof(changeEvent.target.files[0]) === 'object') {
reader.readAsDataURL(changeEvent.target.files[0]);
};
});
},
controller: 'FileUploadCtrl',
template:
'<span class="btn btn-success fileinput-button">' +
'<i class="glyphicon glyphicon-plus"></i>' +
'<span>Replace Image</span>' +
'<input type="file" accept="image/*" name="files[]" multiple="">' +
'</span>',
restrict: 'E'
};
});
For multiple files input using lodash or underscore:
.directive("fileread", [function () {
return {
scope: {
fileread: "="
},
link: function (scope, element, attributes) {
element.bind("change", function (changeEvent) {
return _.map(changeEvent.target.files, function(file){
scope.fileread = [];
var reader = new FileReader();
reader.onload = function (loadEvent) {
scope.$apply(function () {
scope.fileread.push(loadEvent.target.result);
});
}
reader.readAsDataURL(file);
});
});
}
}
}]);
function filesModelDirective(){
return {
controller: function($parse, $element, $attrs, $scope){
var exp = $parse($attrs.filesModel);
$element.on('change', function(){
exp.assign($scope, this.files[0]);
$scope.$apply();
});
}
};
}
app.directive('filesModel', filesModelDirective);
I had to do same on multiple input, so i updated #Endy Tjahjono method.
It returns an array containing all readed files.
.directive("fileread", function () {
return {
scope: {
fileread: "="
},
link: function (scope, element, attributes) {
element.bind("change", function (changeEvent) {
var readers = [] ,
files = changeEvent.target.files ,
datas = [] ;
for ( var i = 0 ; i < files.length ; i++ ) {
readers[ i ] = new FileReader();
readers[ i ].onload = function (loadEvent) {
datas.push( loadEvent.target.result );
if ( datas.length === files.length ){
scope.$apply(function () {
scope.fileread = datas;
});
}
}
readers[ i ].readAsDataURL( files[i] );
}
});
}
}
});
I had to modify Endy's directive so that I can get Last Modified, lastModifiedDate, name, size, type, and data as well as be able to get an array of files. For those of you that needed these extra features, here you go.
UPDATE:
I found a bug where if you select the file(s) and then go to select again but cancel instead, the files are never deselected like it appears. So I updated my code to fix that.
.directive("fileread", function () {
return {
scope: {
fileread: "="
},
link: function (scope, element, attributes) {
element.bind("change", function (changeEvent) {
var readers = [] ,
files = changeEvent.target.files ,
datas = [] ;
if(!files.length){
scope.$apply(function () {
scope.fileread = [];
});
return;
}
for ( var i = 0 ; i < files.length ; i++ ) {
readers[ i ] = new FileReader();
readers[ i ].index = i;
readers[ i ].onload = function (loadEvent) {
var index = loadEvent.target.index;
datas.push({
lastModified: files[index].lastModified,
lastModifiedDate: files[index].lastModifiedDate,
name: files[index].name,
size: files[index].size,
type: files[index].type,
data: loadEvent.target.result
});
if ( datas.length === files.length ){
scope.$apply(function () {
scope.fileread = datas;
});
}
};
readers[ i ].readAsDataURL( files[i] );
}
});
}
}
});
If you want something a little more elegant/integrated, you can use a decorator to extend the input directive with support for type=file. The main caveat to keep in mind is that this method will not work in IE9 since IE9 didn't implement the File API. Using JavaScript to upload binary data regardless of type via XHR is simply not possible natively in IE9 or earlier (use of ActiveXObject to access the local filesystem doesn't count as using ActiveX is just asking for security troubles).
This exact method also requires AngularJS 1.4.x or later, but you may be able to adapt this to use $provide.decorator rather than angular.Module.decorator - I wrote this gist to demonstrate how to do it while conforming to John Papa's AngularJS style guide:
(function() {
'use strict';
/**
* #ngdoc input
* #name input[file]
*
* #description
* Adds very basic support for ngModel to `input[type=file]` fields.
*
* Requires AngularJS 1.4.x or later. Does not support Internet Explorer 9 - the browser's
* implementation of `HTMLInputElement` must have a `files` property for file inputs.
*
* #param {string} ngModel
* Assignable AngularJS expression to data-bind to. The data-bound object will be an instance
* of {#link https://developer.mozilla.org/en-US/docs/Web/API/FileList `FileList`}.
* #param {string=} name Property name of the form under which the control is published.
* #param {string=} ngChange
* AngularJS expression to be executed when input changes due to user interaction with the
* input element.
*/
angular
.module('yourModuleNameHere')
.decorator('inputDirective', myInputFileDecorator);
myInputFileDecorator.$inject = ['$delegate', '$browser', '$sniffer', '$filter', '$parse'];
function myInputFileDecorator($delegate, $browser, $sniffer, $filter, $parse) {
var inputDirective = $delegate[0],
preLink = inputDirective.link.pre;
inputDirective.link.pre = function (scope, element, attr, ctrl) {
if (ctrl[0]) {
if (angular.lowercase(attr.type) === 'file') {
fileInputType(
scope, element, attr, ctrl[0], $sniffer, $browser, $filter, $parse);
} else {
preLink.apply(this, arguments);
}
}
};
return $delegate;
}
function fileInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
element.on('change', function (ev) {
if (angular.isDefined(element[0].files)) {
ctrl.$setViewValue(element[0].files, ev && ev.type);
}
})
ctrl.$isEmpty = function (value) {
return !value || value.length === 0;
};
}
})();
Why wasn't this done in the first place? AngularJS support is intended to reach only as far back as IE9. If you disagree with this decision and think they should have just put this in anyway, then jump the wagon to Angular 2+ because better modern support is literally why Angular 2 exists.
The issue is (as was mentioned before) that without the file api
support doing this properly is unfeasible for the core given our
baseline being IE9 and polyfilling this stuff is out of the question
for core.
Additionally trying to handle this input in a way that is not
cross-browser compatible only makes it harder for 3rd party solutions,
which now have to fight/disable/workaround the core solution.
...
I'm going to close this just as we closed #1236. Angular 2 is being
build to support modern browsers and with that file support will
easily available.
Alternatively you could get the input and set the onchange function:
<input type="file" id="myFileInput" />
document.getElementById("myFileInput").onchange = function (event) {
console.log(event.target.files);
};
Try this,this is working for me in angular JS
let fileToUpload = `${documentLocation}/${documentType}.pdf`;
let absoluteFilePath = path.resolve(__dirname, fileToUpload);
console.log(`Uploading document ${absoluteFilePath}`);
element.all(by.css("input[type='file']")).sendKeys(absoluteFilePath);

Resources