我见过使用指令启用AngularJS来访问文件内容或属性的示例(例如在Alex Such' s fiddle和blog post中)但我会想到以下简单的代码可以工作(它不会)。
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]);
}
};
如果我在第this.content = fileContent
行放置一个断点,我可以看到content
的值从&#34; [等待文件]&#34;并被所选txt文件的内容所取代(在我的情况下&#34; Hallo World&#34;)。 controller.showFileContent(event.target.result)
上的断点显示相同,值变为&#34; [等待文件]&#34; to&#34; Hallo World&#34;。
但是HTML永远不会重新呈现,它仍然是&#34; [等待文件]&#34;。为什么呢?
(N.B。我已将代码放入a fiddle。)
答案 0 :(得分:4)
Angular之外的事件的主要概念是正确的,但是你有两个地方在Angular背景之外:
onchange="grabFileContent()"
会导致所有grabFileContent()
在Angular上下文之外运行fileReader.addEventListener('load', function(event){ ...
导致回调在Angular上下文之外运行我将如何做到这一点。首先,将onchange事件移动到角度上下文和控制器中:
<input id="uploadInput" type="file" name="myFiles" ng-model="ctrl.filename" ng-change="grabFileContent()" />
现在来自你的控制器:
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]);
}
};
});
答案 1 :(得分:1)
当收听AngularJS环境之外的事件(例如DOM事件或您的FileReader
事件)时,您需要在$apply()
调用中包含侦听器代码以触发$digest
和随后更新视图。
fileReader.addEventListener('load', function (event) {
$scope.$apply(function () {
// ... (put your code here)
});
});
您需要以某种方式将范围传递给您的函数。
此外,正如deitch's answer指出的那样,您不应该使用onchange
之类的原生事件处理程序属性,而应使用Angular方法,例如ng-change
。在文件输入的情况下,这不会起作用,并且您可能最好通过创建捕获本机change
事件的指令并使用文件内容更新范围变量:
.directive('fileContent', ['$parse', function ($parse) {
return {
compile: function (element, attrs) {
var getModel = $parse(attrs.fileContent);
return function link ($scope, $element, $attrs) {
$element.on('change', function () {
var reader = new FileReader();
reader.addEventListener('load', function (event) {
$scope.$apply(function () {
// ... (get file content)
getModel($scope).assign(/* put file content here */);
});
});
// ... (read file content)
});
};
}
};
}])
&安培;
<input type="file" file-content="someScopeVariable">
这应该会自动使用当前所选文件的内容更新范围变量。它显示了在#Angular&#34;中思考的典型问题的分离。
答案 2 :(得分:1)
代码in deitch's answer看起来正确,并且确实帮助我理解了事情。但是AngularJS does not support file input binding以及ng-change
将不起作用。查看that GitHub issue中的讨论,这似乎是因为绑定到HTML的文件输入并上传文件被视为同义词,因为实现完整文件API更复杂。但是,我们希望将本地文件中的输入加载到Angular模型中,这是纯粹的本地场景。
我们可以使用Angular指令实现这一点,就像我在问题中提到的fiddle和blog post或者hon2a's editted answer和laurent's answer所做的那样。 / p>
但是为了在没有指令的情况下实现这一点,我们需要处理Angular上下文之外的文件输入onchange
,然后使用Angular的$scope.$apply()
来警告Angular产生的更改。 Here is some code that does this:
HTML:
<div id="ContainingDiv" ng-controller="MainController as ctrl">
<input id="uploadInput" type="file" name="myFiles" onchange="grabFileContent()" />
<br />
{{ ctrl.content }}
</div>
JavaScript的:
var myApp = angular.module('myApp',[]);
myApp.controller('MainController', ['$scope', function ($scope) {
this.content = "[Waiting for File]";
this.showFileContent = function(fileContent){
$scope.$apply((function(controller){
return function(){
controller.content = fileContent;
};
})(this));
};
}]);
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]);
}
};
答案 3 :(得分:1)
我在FileReader API周围编写了一个包装器,您可以找到它here
它基本上包装了Promise中的FileReader原型方法,并确保在$ apply函数中调用事件处理程序,以便完成与Angular的桥接。
如何从指令中使用它来显示图像的预览
,有一个quick example(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);
请注意inputFile指令,该指令允许将File绑定到model属性。当然绑定只是一种方式,因为输入元素不允许设置文件(出于安全原因)