`<input type =“ file ”/>` için ng-model (DEMO direktifi ile)


281

Ben tür dosyası ile giriş etiketinde ng-modelini kullanmaya çalıştım:

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

Ancak bir dosya seçtikten sonra, denetleyicide $ scope.vm.uploadme hala tanımsız.

Seçilen dosyayı denetleyicime nasıl alabilirim?



NgModel kullanırken her zaman html öğesinde name özelliğini belirtmeniz gerektiğine inanıyorum.
Sam

Yanıtlar:


327

Yönerge ile bir geçici çözüm oluşturdum:

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

Ve giriş etiketi:

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

Veya yalnızca dosya tanımı gerekiyorsa:

.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
Ben imme etiketinde src olarak uploadme kullanıyorum, bu yüzden direktif tarafından ayarlandığını görebiliyorum. Ancak, $ scope.uploadme kullanarak denetleyiciden yakalamaya çalışırsanız, "tanımsız" dır. Ancak uploadme'yi kontrol cihazından ayarlayabilirim. Örneğin, $ scope.uploadme = "*" görüntünün kaybolmasını sağlar.
Quest Quest Aronsson,

5
Sorun, direktifin childScope oluşturması ve uploadme'yi bu kapsamda ayarlamasıdır. Orijinal (üst) kapsamın ayrıca childScope'tan etkilenmeyen bir uploadme özelliği vardır. HTML'deki uploadme dosyasını her iki kapsamdan da güncelleyebilirim. Bir childScope oluşturmaktan kaçınmanın bir yolu var mı?
Görev Aradı Aronsson

4
@AlexC iyi soru ng-model çalışma hakkında değil, dosyaları karşıya yükleme hakkında :) O zaman ben dosyayı yüklemek gerek yoktu. Ancak son zamanlarda bu egghead.io öğreticisinden nasıl dosya yükleneceğini öğrendim .
Endy Tjahjono

10
$ scope unutma. $ on ('$ destory', function () {element.unbind ("change");}
Nawlbergs

2
Bir sorum var .... Bu şekilde düz javascript ve html ile karşılaştırıldığında çok karmaşık değil mi? Cidden, bu çözüme ulaşmak için AngularJS'yi gerçekten anlamalısınız ... ve bir javascript olayıyla aynı şeyi yapabilirim. Neden düz JS yolu değil, Açısal yol yapıyorsunuz?
AFP_555

53

Bu yönergeyi kullanıyorum:

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

ve şu şekilde çağırın:

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

(EditItem.editItem._attachments_uri.image) özelliği, veri uri (!) Olarak seçtiğiniz dosyanın içeriğiyle doldurulur.

Bu komut dosyasının hiçbir şey yüklemeyeceğini lütfen unutmayın. Modelinizi yalnızca dosya kodlu reklamınızın içeriğiyle bir data-uri (base64) dolduracaktır.

Çalışan bir demoya buradan göz atın: http://plnkr.co/CMiHKv2BEidM9SShm9Vv


4
Umut verici görünüyorsunuz, lütfen kodun arkasındaki mantığı açıklayabilir ve tarayıcı uyumluluğu hakkında yorum yapabilir misiniz (çoğunlukla IE ve fileAPI olmayan tarayıcı)?
Oleg Belousov

Ayrıca, anlayışımın en iyisine göre, AJAX isteğinin içerik türü başlığını tanımsız olarak ayarlayacak ve bu tür bir alanı sunucuya göndermeye çalışacaksam, tarayıcının fileAPI'yi desteklediği varsayılarak, açısal Doğru mu?
Oleg Belousov

@OlegTikhonov doğru değilsiniz! Bu komut dosyası hiçbir şey göndermeyecek. Base64 dizesi olarak seçtiğiniz dosyayı okuyacak ve modelinizi bu dizeyle güncelleyecektir.
Elmer

@Elmer Evet, anlıyorum, bir dosya alanı (kullanıcının bir FileAPI nesnesindeki kullanıcının makinesindeki dosyaya göreceli bir yol) içeren bir form göndererek, dosyayı XHR isteği ile açısal yüklemeyi yapabilirsiniz. içerik türü başlığını undefined olarak ayarlayarak
Oleg Belousov

1
Overwritting amacı nedir ngModel'ın $renderfonksiyonunu?
sp00m

39

İle <input type="file">çalışmak için nasıl etkinleştirilirng-model

Çalışan Direktifin Çalışma Demosu ng-model

Temel ng-modeldirektif <input type="file">kutudan çıktığı haliyle çalışmaz .

Bu özel yönerge sağlayan ng-modelve etkinleştirme yararı vardır ng-change, ng-requiredve ng-formbirlikte çalışmaya direktifleri <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>


Seçili dosya olup olmadığını kontrol etmek için koşulu kullanabilirsiniz, ng-model undefined **** ise (files.length> 0) {ngModel. $ SetViewValue (files); } else {ngModel. $ setViewValue (tanımsız); }
Farshad Kazemi

Dosyanın verileri nasıl alınır? Ve {{file.name}} gibi kullanabileceğimiz diğer özellikler nelerdir
Adarsh ​​Singh


26

Bu @ endy-tjahjono'nun çözümü için bir ek.

Uploadme değerini kapsamından alamadım . HTML'deki uploadme direktif tarafından görsel olarak güncellenmiş olsa da , yine de değerine $ scope.uploadme ile erişemedim. Yine de değerini kapsamdan belirleyebildim. Gizemli, değil mi?

Anlaşıldığı üzere, direktif tarafından bir alt kapsam oluşturuldu ve alt kapsamın kendi uploadme'si vardı .

Çözüm, uploadme değerini korumak için ilkelden ziyade bir nesne kullanmaktı .

Denetleyicide:

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

ve HTML'de:

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

Direktifte değişiklik yapılmamıştır.

Şimdi, hepsi beklendiği gibi çalışıyor. $ Scope.uploadme kullanarak denetleyicimden uploadme.src değerini alabilirim.


1
Evet, aynen öyle.
Quest Quest Aronsson,

1
Aynı deneyimi onaylıyorum; çok güzel hata ayıklama ve açıklama. Direktifin neden kendi kapsamını oluşturduğundan emin değilim.
Adam Casey

Alternatif olarak, bir satır içi bildirim:$scope.uploadme = { src: '' }
15

9

Merhaba millet i bir direktif oluşturmak ve çardak kayıtlı.

bu lib, sadece dosya verilerini döndürmekle kalmaz, aynı zamanda dataurl veya base 64 dosyalarına da giriş dosyası modellemenize yardımcı olur.

{
    "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/

Umut sana yardım edecek


$ scope kullanarak nasıl kullanılır? Bunu kullanmayı denedim ama hata ayıklarken tanımsız oldum.
Gujarat Santana

1
Nice Work Instagram Hesabındaki Resim ve Videoları yozawiratama! İyi çalışıyor. Ve @GujaratSantana, eğer <input type = "file" ng-file-model = "myDocument" /> ise, yalnızca $ scope.myDocument.name veya genel olarak $ scope.myDocument. <Herhangi bir özellik> kullanın. ["lastModified", "lastModifiedDate", "name", "size", "type", "data"]
Jay Dharmendra Solanki

üzerinden
kurulamaz

birden fazla dosya yüklemek için nasıl kullanıcı?
aldrien.h

reader.readAsDataURLYöntem, eski. Modern kod URL.create ObjectURL () kullanır .
georgeawg

4

Bu, ng-modelinde, kullanımda olduğu gibi, kapsamda özniteliğin adını belirtmenize izin veren biraz değiştirilmiş bir sürümdür:

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

Direktif:

.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 veya alt çizgi kullanarak birden fazla dosya girmek için:

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

Dosya nesnesini döndürdüğü için şeref . DataURL'ye dönüştüren diğer yönergeler dosyayı yüklemek isteyen denetleyicileri zorlaştırır.
georgeawg

2

Birden fazla girişte aynı şeyi yapmak zorunda kaldım, bu yüzden @Endy Tjahjono yöntemini güncelledim. Okunan tüm dosyaları içeren bir dizi döndürür.

  .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

Ben Lasty Değiştirilmiş, lastModifiedDate, adı, boyutu, türü ve veri almak yanı sıra bir dizi dosya almak mümkün böylece Endy yönergesini değiştirmek zorunda kaldı. Bu ekstra özelliklere ihtiyaç duyanlarınız için işte buradasınız.

GÜNCELLEME: Dosya (lar) ı seçip tekrar seçerseniz ancak iptal ederseniz, dosyaların göründüğü gibi hiçbir zaman seçimi kaldırılmazsa bir hata buldum. Bunu düzeltmek için kodumu güncelledim.

 .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

Biraz daha zarif / entegre bir şey istiyorsanız , direktifin desteğiyle genişletilmesi için bir dekoratör kullanabilirsiniz . Akılda tutulması gereken ana uyarı , IE9 Dosya API'sını uygulamadığı için bu yöntemin IE9'da çalışmayacağıdır . XHR aracılığıyla türden bağımsız olarak ikili verileri yüklemek için JavaScript kullanmak IE9 veya daha eski sürümlerde yerel olarak mümkün değildir ( yerel dosya sistemine erişmek için kullanımı ActiveX kullanmak sayılmaz, sadece güvenlik sorunları ister).inputtype=fileActiveXObject

Bu kesin yöntem ayrıca AngularJS 1.4.x veya daha yenisini gerektirir, ancak bunu $provide.decoratoryerine kullanmak için uyarlayabilirsiniz angular.Module.decorator- John Papa'nın AngularJS stil kılavuzuna uyurken nasıl yapılacağını göstermek için bu özeti yazdım :

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

Bu neden ilk etapta yapılmadı? AngularJS desteğinin yalnızca IE9'a kadar ulaşması amaçlanmıştır. Bu karara katılmıyorsanız ve bunu yine de koymaları gerektiğini düşünüyorsanız, vagonu Açısal 2 + 'ya atlayın, çünkü daha iyi modern destek tam anlamıyla Açısal 2'nin var olmasıdır.

Sorun (daha önce de belirtildiği gibi), dosya api desteği olmadan bunu düzgün yapmak temel bizim IE9 ve bu şeyler çok dolgu doldurma çekirdek göz önüne alındığında çekirdek için mümkün değildir.

Ayrıca, bu girdiyi çapraz tarayıcı uyumlu olmayan bir şekilde işlemeye çalışmak, artık çekirdek çözümle mücadele etmek / devre dışı bırakmak / geçici çözüm bulmak zorunda olan 3. taraf çözümleri zorlaştırmaktadır.

...

# 1236'yı kapattığımız gibi bunu kapatacağım. Açısal 2, modern tarayıcıları desteklemek için oluşturuluyor ve bu dosya desteği ile kolayca sunulacak.


-2

Bunu deneyin, bu benim için açısal JS'de çalışıyor

    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);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.