ng-model untuk `<input type =“ file ”/>` (dengan direktif DEMO)

281

Saya mencoba menggunakan ng-model pada tag input dengan tipe file:

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

Tetapi setelah memilih file, di controller, $ scope.vm.uploadme masih belum ditentukan.

Bagaimana cara mendapatkan file yang dipilih di controller saya?

Endy Tjahjono
sumber
3
Lihat stackoverflow.com/a/17923521/135114 , khususnya contoh yang dikutip online di jsfiddle.net/danielzen/utp7j
Daryn
Saya percaya Anda selalu perlu menentukan properti nama pada elemen html saat menggunakan ngModel.
Sam

Jawaban:

327

Saya membuat solusi dengan arahan:

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

Dan tag input menjadi:

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

Atau jika hanya definisi file yang diperlukan:

.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;
                });
            });
        }
    }
}]);
Endy Tjahjono
sumber
4
Saya menggunakan uploadme sebagai src dalam tag img, jadi saya bisa melihatnya sedang diatur oleh arahan. Namun, jika saya mencoba mengambilnya dari controller menggunakan $ scope.uploadme, itu "tidak terdefinisi". Saya dapat mengatur unggah dari controller. Misalnya, $ scope.uploadme = "*" membuat gambar hilang.
Per Quested Aronsson
5
Masalahnya adalah bahwa arahan menciptakan childScope, dan menetapkan uploadme dalam lingkup itu. Cakupan (induk) asli juga memiliki uploadme, yang tidak terpengaruh oleh childScope. Saya dapat memperbarui unggah saya dalam HTML dari lingkup mana pun. Apakah ada cara untuk menghindari membuat childScope sama sekali?
Per Quested Aronsson
4
@AlexC baik pertanyaannya adalah tentang ng-model tidak berfungsi, bukan tentang mengunggah file :) Pada saat itu saya tidak perlu mengunggah file. Namun baru-baru ini saya belajar cara mengunggah file dari tutorial egghead.io ini .
Endy Tjahjono
10
jangan lupa untuk $ scope. $ on ('$ destory', function () {element.unbind ("change");}
Nawlbergs
2
Saya punya pertanyaan .... Bukankah cara ini terlalu rumit dibandingkan dengan javascript dan html biasa? Serius, Anda benar-benar perlu memahami AngularJS untuk mencapai solusi ini ... dan sepertinya saya bisa melakukan hal yang sama dengan acara javascript. Mengapa melakukannya dengan cara Angular dan bukan dengan cara JS biasa?
AFP_555
53

Saya menggunakan arahan ini:

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

dan panggil seperti ini:

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

Properti (editItem.editItem._attachments_uri.image) akan diisi dengan konten file yang Anda pilih sebagai data-uri (!).

Harap perhatikan bahwa skrip ini tidak akan mengunggah apa pun. Itu hanya akan mengisi model Anda dengan isi file Anda yang dikodekan dengan data-uri (base64).

Lihat demo yang berfungsi di sini: http://plnkr.co/CMiHKv2BEidM9SShm9Vv

Elmer
sumber
4
Terlihat menjanjikan, bisakah Anda menjelaskan logika di balik kode, dan berkomentar tentang kompatibilitas browser (kebanyakan browser IE dan fileAPI)?
Oleg Belousov
Juga, untuk yang terbaik dari pemahaman saya, jika saya akan mengatur header tipe konten permintaan AJAX menjadi tidak terdefinisi, dan akan mencoba mengirim bidang seperti itu ke server, angular akan mengunggahnya, dengan asumsi bahwa browser mendukung fileAPI, saya Aku benar?
Oleg Belousov
@OlegTikhonov Anda salah! Skrip ini tidak akan mengirim apa pun. Ini akan membaca file yang Anda pilih sebagai string Base64 dan memperbarui model Anda dengan string itu.
Elmer
@Elmer Ya, saya mengerti, maksud saya adalah dengan mengirim formulir yang berisi bidang file (jalur relatif ke file di mesin pengguna di objek FileAPI), Anda dapat membuat sudut mengunggah file dengan permintaan XHR dengan mengatur header tipe konten ke undefined
Oleg Belousov
1
Apa tujuan dari overwritting ngModel's $renderfungsi?
sp00m
39

Cara mengaktifkan <input type="file">untuk bekerja dengannyang-model

Demo Kerja Petunjuk yang Bekerja dengan ng-model

ng-modelArahan inti tidak bekerja dengan di <input type="file">luar kotak.

Direktif kebiasaan ini memungkinkan ng-modeldan memiliki manfaat tambahan memungkinkan ng-change, ng-requireddan ng-formarahan untuk bekerja dengan <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>

georgeawg
sumber
Anda dapat menggunakan kondisi untuk memeriksa apakah tidak ada file yang dipilih, ng-model tidak terdefinisi **** if (files.length> 0) {ngModel. $ SetViewValue (file); } else {ngModel. $ setViewValue (undefined); }
Farshad Kazemi
Bagaimana cara mendapatkan data file? Dan apa saja atribut lain yang dapat kita gunakan seperti {{file.name}}
Adarsh ​​Singh
26

Ini merupakan tambahan untuk solusi @ endy-tjahjono.

Saya akhirnya tidak bisa mendapatkan nilai unggah dari ruang lingkup. Meskipun uploadme dalam HTML tampak diperbarui oleh arahan, saya masih tidak dapat mengakses nilainya dengan $ scope.uploadme. Saya bisa mengatur nilainya dari ruang lingkup. Misterius, benar ..?

Ternyata, ruang lingkup anak dibuat oleh arahan, dan ruang lingkup anak memiliki uploadme sendiri .

Solusinya adalah menggunakan objek daripada primitif untuk menyimpan nilai uploadme .

Dalam controller saya punya:

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

dan dalam HTML:

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

Tidak ada perubahan pada arahan.

Sekarang, semuanya berfungsi seperti yang diharapkan. Saya bisa mengambil nilai uploadme.src dari controller saya menggunakan $ scope.uploadme.

Per Dicari Aronsson
sumber
1
Yup, memang begitu.
Per Quested Aronsson
1
Saya mengkonfirmasi pengalaman yang sama; debug dan penjelasan yang sangat bagus. Saya tidak yakin mengapa arahan menciptakan ruang lingkup sendiri.
Adam Casey
Atau, deklarasi sebaris:$scope.uploadme = { src: '' }
maxathousand
9

Hai teman-teman saya membuat arahan dan terdaftar di bower.

Lib ini akan membantu Anda memodelkan file input, tidak hanya mengembalikan data file tetapi juga file dataurl atau basis 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/

Semoga akan membantu Anda

yozawiratama
sumber
bagaimana cara menggunakannya menggunakan $ scope? Saya mencoba menggunakan ini tetapi tidak terdefinisi saat debugging.
Gujarat Santana
1
Kerja Bagus yozawiratama! Itu bekerja dengan baik. Dan @GujaratSantana, jika <input type = "file" ng-file-model = "myDocument" /> maka gunakan saja $ scope.myDocument.name atau secara umum $ scope.myDocument. <Properti apa pun> di mana properti menjadi salah satu ["lastModified", "lastModifiedDate", "name", "size", "type", "data"]
Jay Dharmendra Solanki
tidak dapat diinstal melalui
bower
bagaimana cara pengguna untuk mengunggah banyak file?
aldrien.h
The reader.readAsDataURLmetode adalah usang. Kode modern menggunakan URL.create ObjectURL () .
georgeawg
4

Ini adalah versi yang sedikit dimodifikasi yang memungkinkan Anda menentukan nama atribut dalam ruang lingkup, seperti yang akan Anda lakukan dengan ng-model, penggunaan:

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

Pengarahan:

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

    };
});
asiop
sumber
3

Untuk input beberapa file menggunakan lodash atau garis bawah:

.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);
                });
            });
        }
    }
}]);
Uelb
sumber
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);
coder000001
sumber
Kudos untuk mengembalikan objek file . Arahan lain yang mengubahnya menjadi DataURL menyulitkan pengontrol yang ingin mengunggah file.
georgeawg
2

Saya harus melakukan hal yang sama pada beberapa input, jadi saya memperbarui metode @Endy Tjahjono. Ini mengembalikan array yang berisi semua file yang dibaca.

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

      }
    }
  });
Hugo
sumber
1

Saya harus memodifikasi arahan Endy sehingga saya bisa mendapatkan Last Modified, lastModifiedDate, nama, ukuran, jenis, dan data serta bisa mendapatkan array file. Bagi Anda yang membutuhkan fitur tambahan ini, ini dia.

UPDATE: Saya menemukan bug di mana jika Anda memilih file (s) dan kemudian pergi untuk memilih lagi tetapi membatalkan sebagai gantinya, file tidak pernah terpilih seperti itu muncul. Jadi saya memperbarui kode saya untuk memperbaikinya.

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

            }
        }
    });
Parley Hammon
sumber
1

Jika Anda menginginkan sesuatu yang sedikit lebih elegan / terintegrasi, Anda dapat menggunakan dekorator untuk memperluas inputarahan dengan dukungan type=file. Peringatan utama yang perlu diingat adalah bahwa metode ini tidak akan berfungsi di IE9 karena IE9 tidak mengimplementasikan File API . Menggunakan JavaScript untuk mengunggah data biner apa pun jenisnya melalui XHR sama sekali tidak mungkin secara bawaan di IE9 atau sebelumnya (penggunaan ActiveXObjectuntuk mengakses sistem file lokal tidak masuk hitungan karena menggunakan ActiveX hanya meminta masalah keamanan).

Metode yang tepat ini juga memerlukan AngularJS 1.4.x atau yang lebih baru, tetapi Anda mungkin dapat mengadaptasi ini untuk digunakan $provide.decoratordaripada angular.Module.decorator- Saya menulis inti ini untuk menunjukkan cara melakukannya sambil menyesuaikan dengan panduan gaya 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;
        };
    }
})();

Mengapa ini tidak dilakukan sejak awal? Dukungan AngularJS dimaksudkan untuk menjangkau hanya sejauh IE9. Jika Anda tidak setuju dengan keputusan ini dan berpikir mereka seharusnya memasukkannya, maka lompati kereta ke Angular 2+ karena dukungan modern yang lebih baik adalah alasan mengapa Angular 2 ada.

Masalahnya adalah (seperti yang disebutkan sebelumnya) bahwa tanpa dukungan file api melakukan ini dengan benar tidak layak untuk inti mengingat baseline kami adalah IE9 dan polyfilling hal ini adalah keluar dari pertanyaan untuk core.

Selain itu mencoba menangani input ini dengan cara yang tidak kompatibel lintas-browser hanya mempersulit solusi pihak ketiga, yang sekarang harus berjuang / menonaktifkan / menyelesaikan solusi inti.

...

Saya akan menutup ini sama seperti kita menutup # 1236. Angular 2 sedang dibangun untuk mendukung browser modern dan dengan itu dukungan file akan mudah tersedia.

p0lar_bear
sumber
-2

Coba ini, ini bekerja untuk saya di JS sudut

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