JavaScript和AngularJS的简单混合不显示HTML中的文件内容

时间:2014-12-15 17:18:45

标签: angularjs

我见过使用指令启用AngularJS来访问文件内容或属性的示例(例如在Alex Such' s fiddleblog 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。)

4 个答案:

答案 0 :(得分:4)

Angular之外的事件的主要概念是正确的,但是你有两个地方在Angular背景之外:

  1. onchange="grabFileContent()"会导致所有grabFileContent()在Angular上下文之外运行
  2. fileReader.addEventListener('load', function(event){ ...导致回调在Angular上下文之外运行
  3. 我将如何做到这一点。首先,将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指令实现这一点,就像我在问题中提到的fiddleblog post或者hon2a's editted answerlaurent'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属性。当然绑定只是一种方式,因为输入元素不允许设置文件(出于安全原因)