ng-model cho `<input type = Tập tin trực tuyến '/` `(với chỉ thị DEMO)


281

Tôi đã thử sử dụng ng-model trên thẻ đầu vào với loại tệp:

<input type="file" ng-model="vm.uploadme" />

Nhưng sau khi chọn một tệp, trong bộ điều khiển, $ scope.vm.uploadme vẫn chưa được xác định.

Làm cách nào để có được tệp đã chọn trong bộ điều khiển của tôi?


3
Xem stackoverflow.com/a/17923521/135114 , đặc biệt là ví dụ được trích dẫn trực tuyến tại jsfiddle.net/danielzen/utp7j
Daryn

Tôi tin rằng bạn luôn cần chỉ định thuộc tính tên trên thành phần html khi sử dụng ngModel.
Sam

Câu trả lời:


327

Tôi đã tạo một cách giải quyết với chỉ thị:

.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]);
            });
        }
    }
}]);

Và thẻ đầu vào trở thành:

<input type="file" fileread="vm.uploadme" />

Hoặc nếu chỉ cần định nghĩa tệp:

.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;
                });
            });
        }
    }
}]);

4
Tôi sử dụng uploadme như src trong thẻ img, vì vậy tôi có thể thấy nó đang được thiết lập bởi lệnh. Tuy nhiên, nếu tôi cố lấy nó từ bộ điều khiển bằng $ scope.uploadme, thì đó là "không xác định". Tôi có thể thiết lập uploadme từ bộ điều khiển, mặc dù. Ví dụ: $ scope.uploadme = "*" làm cho hình ảnh biến mất.
Per Quested Aronsson

5
Vấn đề là lệnh này tạo ra một ChildScope và đặt uploadme trong phạm vi đó. Phạm vi ban đầu (cha mẹ) cũng có một uploadme, không bị ảnh hưởng bởi childScope. Tôi có thể cập nhật uploadme trong HTML từ một trong hai phạm vi. Có cách nào để tránh tạo ra một kính ngắm con không?
Per Quested Aronsson

4
@AlexC, câu hỏi là về mô hình ng không hoạt động, không phải về việc tải lên các tệp :) Lúc đó tôi không cần phải tải tệp lên. Nhưng gần đây tôi đã học được cách tải lên tập tin từ hướng dẫn egghead.io này .
Endy Tjahjono

10
đừng quên $ scope. $ on ('$ Destory', function () {Element.unbind ("thay đổi");}
Nawlbergs 23/2/2016

2
Tôi có một câu hỏi .... Không phải cách này quá phức tạp so với javascript và html đơn giản sao? Nghiêm túc mà nói, bạn thực sự cần phải hiểu AngularJS để đạt được giải pháp này ... và dường như tôi có thể làm điều tương tự với một sự kiện javascript. Tại sao làm theo cách Angular mà không phải là cách JS đơn giản?
AFP_555

53

Tôi sử dụng chỉ thị này:

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
});

và gọi nó như thế này:

<input type="file" ng-model="editItem._attachments_uri.image" accept="image/*" app-filereader />

Thuộc tính (editItem.editItem._attachments_uri.image) sẽ được điền với nội dung của tệp bạn chọn làm dữ liệu-uri (!).

Xin lưu ý rằng tập lệnh này sẽ không tải lên bất cứ điều gì. Nó sẽ chỉ đưa vào mô hình của bạn với nội dung của tệp được mã hóa quảng cáo của bạn một data-uri (base64).

Kiểm tra một bản demo làm việc ở đây: http://plnkr.co/CMiHKv2BEidM9SShm9Vv


4
Nhìn có vẻ hứa hẹn, bạn có thể vui lòng giải thích logic đằng sau mã và nhận xét về khả năng tương thích trình duyệt (chủ yếu là trình duyệt IE và không phải tệpAPI) không?
Oleg Belousov

Ngoài ra, theo sự hiểu biết của tôi, nếu tôi sẽ đặt tiêu đề loại nội dung của yêu cầu AJAX thành không xác định và sẽ cố gửi một trường như vậy đến máy chủ, angular sẽ tải nó lên, giả sử rằng trình duyệt hỗ trợ tệpAPI, am Tôi đúng?
Oleg Belousov

@OlegTikhonov bạn không đúng! Kịch bản này sẽ không gửi bất cứ điều gì. Nó sẽ đọc tệp bạn đã chọn dưới dạng chuỗi Base64 và cập nhật mô hình của bạn với chuỗi đó.
Elmer

@Elmer Vâng, tôi hiểu, ý tôi là, bằng cách gửi một biểu mẫu có chứa trường tệp (đường dẫn tương đối đến tệp trong máy của người dùng trong đối tượng FileAPI), bạn có thể tạo tệp góc lên tệp theo yêu cầu XHR bằng cách đặt tiêu đề loại nội dung thành không xác định
Oleg Belousov

1
Mục đích của chức năng ghi đè ngModel$rendergì?
sp00m

39

Cách kích hoạt <input type="file">để làm việc vớing-model

Bản demo làm việc của Chỉ thị hoạt động với ng-model

Lệnh cốt lõi ng-modelkhông hoạt động với <input type="file">ra khỏi hộp.

Chỉ thị tùy chỉnh này cho phép ng-modelvà có lợi ích bổ sung cho phép ng-change, ng-requiredng-formchỉ thị để làm việc với <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>


Bạn có thể sử dụng điều kiện để kiểm tra xem không có tệp nào được chọn, mô hình ng không được xác định **** if (files.length> 0) {ngModel. $ SetViewValue (tệp); } other {ngModel. $ setViewValue (không xác định); }
Farshad Kazemi

Làm thế nào để có được dữ liệu của tập tin? Và các thuộc tính khác mà chúng ta có thể sử dụng như {{file.name}}
Adarsh ​​Singh


26

Đây là phần phụ lục cho giải pháp của @ endy-tjahjono.

Tôi đã kết thúc không thể có được giá trị của uploadme từ phạm vi. Mặc dù uploadme trong HTML đã được cập nhật rõ ràng bằng chỉ thị, tôi vẫn không thể truy cập giá trị của nó bằng $ scope.uploadme. Tôi đã có thể thiết lập giá trị của nó từ phạm vi, mặc dù. Bí ẩn, phải không ..?

Khi nó bật ra, một phạm vi con được tạo bởi lệnh và phạm vi con có uploadme riêng của nó .

Giải pháp là sử dụng một đối tượng chứ không phải là nguyên thủy để giữ giá trị của uploadme .

Trong bộ điều khiển tôi có:

$scope.uploadme = {};
$scope.uploadme.src = "";

và trong HTML:

 <input type="file" fileread="uploadme.src"/>
 <input type="text" ng-model="uploadme.src"/>

Không có thay đổi đối với chỉ thị.

Bây giờ, tất cả hoạt động như mong đợi. Tôi có thể lấy giá trị của uploadme.src từ bộ điều khiển của mình bằng cách sử dụng $ scope.uploadme.


1
Yup, chính xác là như vậy.
Per Quested Aronsson

1
Tôi xác nhận trải nghiệm tương tự; gỡ lỗi và giải thích rất tốt đẹp. Tôi không chắc tại sao lệnh này lại tạo ra phạm vi của riêng nó.
Adam Casey

Ngoài ra, một tuyên bố nội tuyến:$scope.uploadme = { src: '' }
maxathousand

9

Xin chào các bạn tôi tạo ra một chỉ thị và đăng ký trên Bower.

lib này sẽ giúp bạn mô hình hóa tệp đầu vào, không chỉ trả về dữ liệu tệp mà còn cả tệp dataurl hoặc cơ sở 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/

Hy vọng sẽ giúp bạn


Làm thế nào để sử dụng nó bằng $ scope? Tôi đã thử sử dụng cái này nhưng không xác định được khi gỡ lỗi.
Gujarat Santana

1
Làm tốt lắm yozawiratama! Nó hoạt động tốt. Và @GujaratSantana, nếu <input type = "file" ng-file-model = "myDocument" /> thì chỉ cần sử dụng $ scope.myDocument.name hoặc nói chung $ scope.myDocument. <Bất động sản> trong đó thuộc tính là một trong những ["lastModified", "lastModifiedDate", "name", "size", "type", "data"]
Jay Dharmendra Solanki

không thể được cài đặt qua Bower
Pimgd

Làm thế nào để người dùng tải lên nhiều tập tin?
aldrien.h

Các reader.readAsDataURLphương pháp là lỗi thời. Mã hiện đại sử dụng URL.create ObjectURL () .
georgeawg

4

Đây là phiên bản sửa đổi một chút cho phép bạn chỉ định tên của thuộc tính trong phạm vi, giống như bạn làm với ng-model, cách sử dụng:

    <myUpload key="file"></myUpload>

Chỉ thị:

.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'

    };
});

3

Đối với nhiều tệp đầu vào bằng lodash hoặc gạch dưới:

.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);
                });
            });
        }
    }
}]);

3

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);

Kudos để trả về đối tượng tập tin . Các chỉ thị khác chuyển đổi nó thành DataURL gây khó khăn cho các bộ điều khiển muốn tải tệp lên.
georgeawg

2

Tôi đã phải làm tương tự trên nhiều đầu vào, vì vậy tôi đã cập nhật phương pháp @Endy Tjahjono. Nó trả về một mảng chứa tất cả các tệp đã đọc.

  .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] );
          }
        });

      }
    }
  });

1

Tôi đã phải sửa đổi chỉ thị của Endyy để tôi có thể nhận được Sửa đổi lần cuối, LastModifiedDate, tên, kích thước, loại và dữ liệu cũng như có thể nhận được một loạt các tệp. Đối với những người bạn cần các tính năng bổ sung này, ở đây bạn đi.

CẬP NHẬT: Tôi đã tìm thấy một lỗi trong đó nếu bạn chọn (các) tệp và sau đó đi đến chọn lại nhưng thay vào đó, các tệp không bao giờ được bỏ chọn như nó xuất hiện. Vì vậy, tôi đã cập nhật mã của mình để sửa lỗi đó.

 .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] );
                    }
                });

            }
        }
    });

1

Nếu bạn muốn một cái gì đó thanh lịch / tích hợp hơn một chút, bạn có thể sử dụng một công cụ trang trí để mở rộng inputchỉ thị với sự hỗ trợ cho type=file. Lưu ý chính cần lưu ý là phương pháp này sẽ không hoạt động trong IE9 do IE9 không triển khai API tệp . Sử dụng JavaScript để tải lên dữ liệu nhị phân bất kể loại thông qua XHR đơn giản là không thể có trong IE9 hoặc trước đó (sử dụng ActiveXObjectđể truy cập hệ thống tệp cục bộ không được tính là sử dụng ActiveX chỉ yêu cầu các vấn đề bảo mật).

Phương pháp chính xác này cũng yêu cầu AngularJS 1.4.x trở lên, nhưng bạn có thể điều chỉnh cách này để sử dụng $provide.decoratorchứ không phải angular.Module.decorator- Tôi đã viết ý chính này để trình bày cách thực hiện trong khi tuân thủ hướng dẫn kiểu AngularJS của John Papa :

(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;
        };
    }
})();

Tại sao điều này không được thực hiện ở nơi đầu tiên? Hỗ trợ AngularJS chỉ nhằm mục đích trở lại xa như IE9. Nếu bạn không đồng ý với quyết định này và nghĩ rằng dù sao họ cũng nên đưa nó vào, thì hãy nhảy chiếc xe sang Angular 2+ vì sự hỗ trợ hiện đại tốt hơn chính là lý do tại sao Angular 2 tồn tại.

Vấn đề là (như đã được đề cập trước đó) rằng không có hỗ trợ api tập tin làm điều này đúng cách là không khả thi đối với lõi do đường cơ sở của chúng tôi là IE9 và việc điền vào công cụ này không nằm trong câu hỏi đối với lõi.

Ngoài ra, việc cố gắng xử lý đầu vào này theo cách không tương thích với nhiều trình duyệt chỉ khiến cho các giải pháp của bên thứ 3 trở nên khó khăn hơn, hiện phải chống lại / vô hiệu hóa / giải quyết giải pháp cốt lõi.

...

Tôi sẽ đóng cái này khi chúng tôi đóng # 1236. Angular 2 đang được xây dựng để hỗ trợ các trình duyệt hiện đại và với sự hỗ trợ tập tin đó sẽ dễ dàng có sẵn.


-2

Hãy thử cái này, cái này hoạt động với tôi trong JS góc cạnh

    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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.