ng-model สำหรับ `<input type =“ file” />` (พร้อมคำสั่งสาธิต)


281

ฉันพยายามใช้ ng-model บนแท็กอินพุตพร้อมไฟล์ประเภท:

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

แต่หลังจากเลือกไฟล์ในตัวควบคุม $ scope.vm.uploadme ยังคงไม่ได้กำหนด

ฉันจะรับไฟล์ที่เลือกในคอนโทรลเลอร์ได้อย่างไร


3
ดูstackoverflow.com/a/17923521/135114โดยเฉพาะตัวอย่างที่อ้างถึงออนไลน์ที่jsfiddle.net/danielzen/utp7j
Daryn

ฉันเชื่อว่าคุณจำเป็นต้องระบุคุณสมบัติชื่อในองค์ประกอบ html เสมอเมื่อใช้ ngModel
Sam

คำตอบ:


327

ฉันสร้างวิธีแก้ปัญหาด้วยคำสั่ง:

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

และแท็กอินพุตจะกลายเป็น:

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

หรือถ้าเพียงแค่ต้องการคำจำกัดความของไฟล์:

.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
ฉันใช้ uploadme เป็น src ในแท็ก img ดังนั้นฉันจะเห็นว่ามันถูกกำหนดโดยคำสั่ง อย่างไรก็ตามถ้าฉันพยายามที่จะคว้ามันจากตัวควบคุมโดยใช้ $ scope.uploadme มันคือ "undefined" ฉันสามารถตั้งค่า uploadme จากคอนโทรลเลอร์ได้ ตัวอย่างเช่น $ scope.uploadme = "*" ทำให้รูปภาพหายไป
ต่อการค้นหา Aronsson

5
ปัญหาคือว่าคำสั่งสร้าง childScope และตั้ง uploadme ในขอบเขตนั้น ขอบเขตเดิม (parent) นั้นมี uploadme ซึ่งไม่ได้รับผลกระทบจาก childScope ฉันสามารถอัปเดต uploadme ใน HTML จากขอบเขตใดก็ได้ มีวิธีหลีกเลี่ยงการสร้าง childScope เลยหรือไม่?
ต่อ Aredson Quested

4
@AlexC ดีคำถามเกี่ยวกับ ng-model ไม่ทำงานไม่เกี่ยวกับการอัปโหลดไฟล์ :) ในเวลานั้นฉันไม่จำเป็นต้องอัปโหลดไฟล์ แต่เมื่อเร็ว ๆ นี้ฉันได้เรียนรู้วิธีอัปโหลดไฟล์จากบทแนะนำ egghead.ioนี้
Endy Tjahjono

10
อย่าลืม $ $ $ on ('$ destory', function () {element.unbind ("change");}
Nawlbergs

2
ฉันมีคำถาม .... วิธีนี้ไม่ซับซ้อนเกินไปเทียบกับ javascript ธรรมดาและ html หรือไม่? อย่างจริงจังคุณต้องเข้าใจ AngularJS เพื่อเข้าถึงโซลูชันนี้ ... และดูเหมือนว่าฉันจะทำเช่นเดียวกันกับเหตุการณ์จาวาสคริปต์ ทำไมต้องทำแบบ Angular และไม่ใช่แบบ JS ธรรมดา?
AFP_555

53

ฉันใช้คำสั่งนี้:

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

และเรียกมันเช่นนี้

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

คุณสมบัติ (editItem.editItem._attachments_uri.image) จะถูกเติมด้วยเนื้อหาของไฟล์ที่คุณเลือกเป็น data-uri (!)

โปรดทราบว่าสคริปต์นี้จะไม่อัปโหลดอะไร มันจะเติมโมเดลของคุณด้วยเนื้อหาของไฟล์ที่เข้ารหัสและ data-uri (base64)

ตรวจสอบตัวอย่างการทำงานได้ที่นี่: http://plnkr.co/CMiHKv2BEidM9SShm9Vv


4
ดูสัญญาคุณช่วยอธิบายตรรกะเบื้องหลังโค้ดและแสดงความคิดเห็นเกี่ยวกับความเข้ากันได้ของเบราว์เซอร์ (IE และเบราว์เซอร์ที่ไม่ใช่ fileAPI เป็นส่วนใหญ่) ได้หรือไม่?
Oleg Belousov

นอกจากนี้เพื่อความเข้าใจที่ดีที่สุดของฉันหากฉันจะตั้งค่าส่วนหัวของประเภทเนื้อหาของคำขอ AJAX เป็นไม่ได้กำหนดและจะพยายามส่งฟิลด์ดังกล่าวไปยังเซิร์ฟเวอร์ Angular จะอัปโหลดสมมติว่าเบราว์เซอร์รองรับ fileAPI ฉันถูกต้องหรือไม่
Oleg Belousov

@OlegTikhonov คุณไม่ถูกต้อง! สคริปต์นี้จะไม่ส่งอะไรเลย มันจะอ่านไฟล์ที่คุณเลือกเป็นสตริง Base64 และอัปเดตโมเดลของคุณด้วยสตริงนั้น
Elmer

@Elmer ใช่ฉันเข้าใจสิ่งที่ฉันหมายถึงคือโดยการส่งแบบฟอร์มที่มีเขตข้อมูลไฟล์ (เส้นทางสัมพัทธ์ไปยังไฟล์ในเครื่องของผู้ใช้ในวัตถุ FileAPI) คุณสามารถทำการอัปโหลดเชิงมุมไฟล์โดยการร้องขอ XHR โดยการตั้งค่าส่วนหัวของประเภทเนื้อหาเป็น undefined
Oleg Belousov

1
วัตถุประสงค์ของการ overwritting คืออะไรngModel's $renderฟังก์ชั่น?
sp00m

39

วิธีเปิดใช้<input type="file">งานให้ทำงานด้วยng-model

สาธิตการทำงานของ Directive ที่ทำงานร่วมกับ ng-model

ng-modelคำสั่งหลักไม่สามารถใช้งานได้<input type="file">ทันที

คำสั่งที่กำหนดเองซึ่งจะช่วยให้ng-modelมีประโยชน์เพิ่มของการใช้งานng-change, ng-requiredและสั่งการทำงานด้วยng-form<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>


คุณสามารถใช้เงื่อนไขเพื่อตรวจสอบว่าไม่มีไฟล์ที่เลือกหรือไม่ ng-model ไม่ได้ถูกกำหนด **** ถ้า (files.length> 0) {ngModel. $ setViewValue (ไฟล์); } else {ngModel. $ setViewValue (ไม่ได้กำหนด); }
Farshad Kazemi

จะรับข้อมูลไฟล์ได้อย่างไร? และอะไรคือคุณสมบัติอื่น ๆ ที่เราสามารถใช้ได้เช่น {{file.name}}
Adarsh ​​Singh


26

นี่เป็นภาคผนวกของโซลูชันของ @ endy-tjahjono

ฉันสิ้นสุดไม่สามารถรับค่าของuploadmeจากขอบเขตได้ แม้ว่าuploadmeใน HTML นั้นถูกปรับปรุงอย่างชัดเจนโดยคำสั่ง แต่ฉันก็ยังไม่สามารถเข้าถึงค่าของมันได้โดย $ scope.uploadme แต่ฉันสามารถกำหนดค่าจากขอบเขตได้ ลึกลับใช่มั้ย .. ?

ที่จะเปิดออก, ขอบเขตเด็กถูกสร้างขึ้นโดยคำสั่งและขอบเขตเด็กมีของตัวเองuploadme

การแก้ปัญหาคือการใช้วัตถุมากกว่าดั้งเดิมที่จะถือค่าของuploadme

ในตัวควบคุมฉันมี:

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

และใน HTML:

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

ไม่มีการเปลี่ยนแปลงคำสั่ง

ตอนนี้มันทำงานได้อย่างที่คาดไว้ ฉันสามารถคว้าค่าของuploadme.srcจากคอนโทรลเลอร์ของฉันโดยใช้ $ scope.uploadme


1
ใช่นั่นแหละ
ต่อ Aredson Quested

1
ฉันยืนยันประสบการณ์เดียวกัน; การแก้ปัญหาและคำอธิบายที่ดีมาก ฉันไม่แน่ใจว่าทำไมคำสั่งสร้างขอบเขตของตัวเอง
Adam Casey

อีกวิธีหนึ่งคือการประกาศแบบอินไลน์:$scope.uploadme = { src: '' }
maxathousand และ

9

สวัสดีพวกฉันสร้างคำสั่งและลงทะเบียนในซุ้ม

lib นี้จะช่วยให้คุณสร้างแบบจำลองไฟล์อินพุตไม่เพียง แต่ส่งคืนข้อมูลไฟล์ แต่ยังรวมถึงไฟล์ดาต้าลิงค์หรือเบส 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/

หวังว่าจะช่วยคุณ


วิธีการใช้โดยใช้ $ scope? ฉันพยายามใช้สิ่งนี้ แต่ไม่ได้กำหนดเมื่อทำการดีบัก
คุชราต Santana

1
การทำงานที่ดี yozawiratama! มันใช้งานได้ดี และ @GujaratSantana ถ้า <input type = "file" ng-file-model = "myDocument" /> จากนั้นใช้ $ scope.myDocument.name หรือโดยทั่วไป $ scope.myDocument $ คุณสมบัติใด ๆ > ที่คุณสมบัติเป็นหนึ่งใน ["lastModified", "lastModifiedDate", "name", "size", "type", "data"]
Jay Dharmendra Solanki

ไม่สามารถติดตั้งผ่าน bower ได้
Pimgd

ผู้ใช้จะอัพโหลดหลายไฟล์ได้อย่างไร?
aldrien.h

reader.readAsDataURLวิธีการเป็นล้าสมัย รหัสที่ทันสมัยใช้URL.create ObjectURL ()
georgeawg

4

นี่เป็นรุ่นที่ได้รับการแก้ไขเล็กน้อยซึ่งให้คุณระบุชื่อของแอททริบิวในขอบเขตเช่นเดียวกับที่คุณใช้กับ ng-model การใช้งาน:

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

Directive:

.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

สำหรับอินพุตไฟล์หลายไฟล์โดยใช้ lodash หรือขีดล่าง:

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

ความรุ่งโรจน์สำหรับการส่งคืนวัตถุไฟล์ คำสั่งอื่นที่แปลงเป็น DataURL ทำให้ตัวควบคุมที่ต้องการอัปโหลดไฟล์เป็นเรื่องยาก
georgeawg

2

ฉันต้องทำแบบเดียวกันกับหลายอินพุตดังนั้นฉันจึงอัปเดตวิธีการ @Endy Tjahjono มันจะส่งกลับอาร์เรย์ที่มีไฟล์ที่อ่านทั้งหมด

  .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

ฉันต้องแก้ไขคำสั่งของ Endy เพื่อที่ฉันจะได้รับ Last Modified, lastModifiedDate, ชื่อ, ขนาด, ประเภทและข้อมูลรวมทั้งสามารถรับอาร์เรย์ของไฟล์ได้ สำหรับผู้ที่ต้องการคุณสมบัติพิเศษเหล่านี้ไปเลย

อัปเดต: ฉันพบข้อผิดพลาดที่ถ้าคุณเลือกไฟล์แล้วไปที่เลือกอีกครั้ง แต่ยกเลิกแทนไฟล์จะไม่ถูกยกเลิกการเลือกเหมือนที่ปรากฏ ดังนั้นฉันจึงปรับปรุงรหัสของฉันเพื่อแก้ไข

 .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

ถ้าคุณต้องการสิ่งเล็ก ๆ น้อย ๆ สง่างามมากขึ้น / บูรณาการ, คุณสามารถใช้มัณฑนากรที่จะขยายคำสั่งด้วยการสนับสนุนinput type=fileข้อแม้สำคัญที่ต้องเก็บไว้ในใจก็คือว่าวิธีการนี้จะไม่ทำงานใน IE9 ตั้งแต่ IE9 ไม่ได้ใช้ไฟล์ของ API การใช้ JavaScript เพื่ออัปโหลดข้อมูลไบนารีโดยไม่คำนึงถึงประเภทผ่าน XHR นั้นเป็นไปไม่ได้ใน IE9 หรือก่อนหน้านี้ (การใช้ActiveXObjectการเข้าถึงระบบไฟล์ในเครื่องไม่นับว่าใช้ ActiveX เพียงเพื่อขอปัญหาด้านความปลอดภัย)

วิธีการที่แน่นอนนี้ยังต้องใช้ AngularJS 1.4.x หรือใหม่กว่า แต่คุณอาจปรับตัวให้ใช้งานได้$provide.decoratorมากกว่าangular.Module.decorator- ฉันเขียนส่วนสำคัญนี้เพื่อสาธิตวิธีการใช้งานในขณะที่สอดคล้องกับแนวทางสไตล์ AngularJS ของ 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;
        };
    }
})();

ทำไมสิ่งนี้ไม่ได้เกิดขึ้นตั้งแต่แรก? การสนับสนุน AngularJS มีจุดประสงค์เพื่อเข้าถึงเฉพาะ IE9 เท่านั้น หากคุณไม่เห็นด้วยกับการตัดสินใจนี้และคิดว่าพวกเขาควรจะใส่มันลงไปแล้วกระโดดเกวียนไปที่ Angular 2+ เพราะการสนับสนุนที่ทันสมัยกว่านั้นคือเหตุผลว่าทำไม Angular 2 จึงมีอยู่

ปัญหาคือ (ดังที่ได้กล่าวไว้ก่อน) ว่าหากไม่มีการสนับสนุนไฟล์ api ที่ทำอย่างถูกต้องจะไม่สามารถทำได้สำหรับแกนหลักเนื่องจากพื้นฐานของเราคือ IE9 และการเติมโพลีฟิลสิ่งนี้ออกจากคำถามหลัก

นอกจากนี้การพยายามจัดการอินพุตนี้ในลักษณะที่ไม่เข้ากันข้ามเบราว์เซอร์ทำให้ยากขึ้นสำหรับโซลูชันของบุคคลที่สามซึ่งตอนนี้ต้องต่อสู้ / ปิดการใช้งาน / แก้ไขปัญหาหลัก

...

ฉันจะปิดตัวเองทันทีที่เราปิด # 1236 Angular 2 กำลังสร้างเพื่อรองรับเบราว์เซอร์รุ่นใหม่และด้วยการรองรับไฟล์นั้นจะสามารถใช้งานได้ง่าย


-2

ลองนี่สิมันใช้กับฉันได้ใน JS เชิงมุม

    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);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.