I have seen examples that use directives to enable AngularJS to access the content or properties of a file (for example in Alex Such's fiddle and blog post) but I would have thought the following simple code would work (it doesn't).
HTML:
<body ng-app="myapp">
<div id="ContainingDiv" ng-controller="MainController as ctrl">
<input id="uploadInput" type="file" name="myFiles" onchange="grabFileContent()" />
<br />
{{ ctrl.content }}
</div>
</body>
JavaScript:
var myapp = angular.module('myapp', []);
myapp.controller('MainController', function () {
this.content = "[Waiting for File]";
this.showFileContent = function(fileContent){
this.content = fileContent;
};
});
var grabFileContent = function() {
var files = document.getElementById("uploadInput").files;
if (files && files.length > 0) {
var fileReader = new FileReader();
fileReader.addEventListener("load", function(event) {
var controller = angular.element(document.getElementById('ContainingDiv')).scope().ctrl;
controller.showFileContent(event.target.result);
});
fileReader.readAsText(files[0]);
}
};
If I place a breakpoint on the line this.content = fileContent
I can see that the value of content
changes from "[Waiting for File]" and is replaced by the content of the chosen txt file (in my case "Hallo World"). A breakpoint on controller.showFileContent(event.target.result)
shows the same, the value changes from "[Waiting for File]" to "Hallo World".
But the HTML never re-renders, it stays as "[Waiting for File]". Why?
(N.B. I've put the code in a fiddle.)
The main concept of events outside Angular is correct, but you have 2 places you are going outside of the Angular context:
onchange="grabFileContent()"
causes all of grabFileContent()
to be run outside of the Angular contextfileReader.addEventListener('load', function(event){ ...
causes the callback to be run outside of the Angular contextHere is how I would do it. First, move the onchange event into the angular context and the controller:
<input id="uploadInput" type="file" name="myFiles" ng-model="ctrl.filename" ng-change="grabFileContent()" />
And now from within your controller:
myapp.controller('MainController', function ($scope) {
this.content = "[Waiting for File]";
this.showFileContent = function(fileContent){
this.content = fileContent;
};
this.grabFileContent = function() {
var that = this, files = this.myFiles; // all on the scope now
if (files && files.length > 0) {
var fileReader = new FileReader();
fileReader.addEventListener("load", function(event) {
// this will still run outside of the Angular context, so we need to
// use $scope.$apply(), but still...
// much simpler now that we have the context for the controller
$scope.$apply(function(){
that.showFileContent(event.target.result);
});
});
fileReader.readAsText(files[0]);
}
};
});
I wrote a wrapper around the FileReader API, which you can find here
It basically wraps FileReader prototype methods in Promise and make sure the event handlers are called within an $apply function so the bridge with Angular is done.
There is a quick example on how to use it from a directive to display preview of image
(function (ng) {
'use strict';
ng.module('app', ['lrFileReader'])
.controller('mainCtrl', ['$scope', 'lrFileReader', function mainCtrl($scope, lrFileReader) {
$scope.$watch('file', function (newValue, oldValue) {
if (newValue !== oldValue) {
lrFileReader(newValue[0])
.on('progress', function (event) {
$scope.progress = (event.loaded / event.total) * 100;
console.log($scope.progress);
})
.on('error', function (event) {
console.error(event);
})
.readAsDataURL()
.then(function (result) {
$scope.image = result;
});
}
});
}])
.directive('inputFile', function () {
return{
restrict: 'A',
require: 'ngModel',
link: function linkFunction(scope, element, attrs, ctrl) {
//view->model
element.bind('change', function (evt) {
evt = evt.originalEvent || evt;
scope.$apply(function () {
ctrl.$setViewValue(evt.target.files);
});
});
//model->view
ctrl.$render = function () {
//does not support two way binding
};
}
};
});
})(angular);
Note the inputFile directive which allow the binding with a File to a model property. Of course the binding is only one way as the input element does not allow to set the file (for security reason)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With