Простая смесь JavaScript и AngularJS, не отображающая содержимое файла из HTML <input type="file"
Я видел примеры, которые используют директивы, позволяющие AngularJS получать доступ к содержимому или свойствам файла (например, в Alex Such fiddle и сообщение в блоге), но я бы подумал, что следующий простой код будет работать (это не так).
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
изменяется с "[Ожидание файла]" и заменяется содержимым выбранного файла txt (в моем случае "Hallo" Мир"). Точка останова на controller.showFileContent(event.target.result)
показывает то же самое, значение изменяется от "[Ожидание файла]" до "Hallo World".
Но HTML никогда не обновляется, он остается как "[Ожидание файла]". Почему?
(N.B. Я поставил код в скрипку.)
Ответы
Ответ 1
Код в ответе deitch выглядит правильно и, безусловно, помог мне понять вещи. Но AngularJS не поддерживает привязку ввода файлов, поэтому ng-change
не будет работать. Глядя на обсуждение в что проблема GitHub, похоже, связано с тем, что привязка к вводу файла HTML и его загрузка воспринимаются как синонимы и потому что реализация API полного файла более сложный. Однако существуют чисто локальные сценарии, в которых мы хотим загрузить входные данные из локального файла в модель Angular.
Мы могли бы получить это с помощью директивы Angular как Alex. Это делает в fiddle и сообщение в блоге Я упоминаю в вопросе или как hon2a отредактированный ответ и laurent answer do.
Но для достижения этого без директив нам необходимо обработать входной файл onchange
вне контекста Angular, но затем использовать Angular $scope.$apply()
для предупреждения Angular к полученным изменениям. Вот код, который делает это:
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]);
}
};
Ответ 2
Основная концепция событий вне Angular правильная, но у вас есть 2 места, которые вы выходите за пределы контекста Angular:
-
onchange="grabFileContent()"
заставляет все grabFileContent()
запускаться за пределами контекста Angular
-
fileReader.addEventListener('load', function(event){ ...
заставляет обратный вызов запускаться вне контекста Angular
Вот как я это сделаю. Сначала переместите событие onchange в контекст Angular и контроллер:
<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]);
}
};
});
Ответ 3
При прослушивании событий вне среды AngularJS (таких как события DOM или событие FileReader
) вам необходимо обернуть код слушателя в вызове $apply()
, чтобы правильно запустить a $digest
и впоследствии обновить представление.
fileReader.addEventListener('load', function (event) {
$scope.$apply(function () {
// ... (put your code here)
});
});
Вам нужно будет каким-то образом передать область своей функции.
Кроме того, как указывает deitch 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".
Ответ 4
Я написал обертку вокруг API FileReader, который вы можете найти здесь
Он в основном обертывает прототипы FileReader в Promise и гарантирует, что обработчики событий вызываются в функции $apply, поэтому выполняется мост с Angular.
Существует быстрый пример о том, как использовать его из директивы для предварительного просмотра изображения
(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, которая позволяет привязывать к файлу свойство модели. Конечно, привязка - это только один способ, поскольку входной элемент не позволяет установить файл (по соображениям безопасности)