it-swarm.com.de

ng-model für `<input type =" file "/>` (mit Direktive DEMO)

Ich habe versucht, ng-model am input-Tag mit der Typdatei zu verwenden:

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

Nach der Auswahl einer Datei im Controller ist $ scope.vm.uploadme noch nicht definiert.

Wie erhalte ich die ausgewählte Datei in meinem Controller?

266
Endy Tjahjono

Ich habe eine Problemumgehung mit Direktive erstellt:

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

Und das Eingabe-Tag wird zu:

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

Oder wenn nur die Dateidefinition benötigt wird:

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

Ich benutze diese Direktive:

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

und ruf es so an:

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

Die Eigenschaft (editItem.editItem._attachments_uri.image) wird mit dem Inhalt der Datei gefüllt, die Sie als Daten-URI (!) Ausgewählt haben.

Bitte beachten Sie, dass dieses Skript nichts hochlädt. Ihr Modell wird nur mit dem Inhalt Ihrer datei-codierten Anzeige und einem Daten-URI (base64) gefüllt.

Sehen Sie sich eine funktionierende Demo hier an: http://plnkr.co/CMiHKv2BEidM9SShm9Vv

53
Elmer

Dies ist ein Nachtrag zur Lösung von @ endy-tjahjono. 

Am Ende konnte ich den Wert von uploadme nicht aus dem Geltungsbereich abrufen. Obwohl uploadme im HTML-Code durch die Direktive sichtbar aktualisiert wurde, konnte ich immer noch nicht über $ scope.uploadme auf seinen Wert zugreifen. Ich konnte seinen Wert jedoch aus dem Geltungsbereich bestimmen. Geheimnisvoll, richtig ..?

Wie sich herausstellte, wurde von der Direktive ein untergeordneter Bereich erstellt, und der untergeordnete Bereich hatte seinen eigenen uploadme

Die Lösung bestand darin, ein Objekt anstelle eines Primitivs zu verwenden, um den Wert von uploadme zu speichern.

In der Steuerung habe ich: 

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

und im HTML:

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

Es gibt keine Änderungen an der Richtlinie. 

Jetzt funktioniert alles wie erwartet. Ich kann den Wert von uploadme.src von meinem Controller mithilfe von $ scope.uploadme abrufen.

27

Arbeitsdemo der Richtlinie, die mit ng-model arbeitet

So aktivieren Sie <input type="file"> für die Zusammenarbeit mit ng-model

Die ng-model-Direktive funktioniert nicht direkt mit <input type="file".

Diese benutzerdefinierte Anweisung aktiviert ng-model und bietet den zusätzlichen Vorteil, dass die Anweisungen ng-change, ng-required und ng-form mit <input type="file"> arbeiten können. 

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>

22
georgeawg

Hallo Jungs, ich erstelle eine Direktive und registriert auf Laube.

diese lib hilft Ihnen bei der Modellierung der Eingabedatei. Sie gibt nicht nur Dateidaten zurück, sondern auch Dateidaten oder 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/

Hoffnung wird dir helfen

9
yozawiratama

Dies ist eine leicht modifizierte Version, mit der Sie den Namen des Attributs im Gültigkeitsbereich angeben können, genau wie bei ng-model. Verwendung: 

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

Richtlinie:

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

    };
});
4
asiop

Für die Eingabe mehrerer Dateien mit lodash oder Unterstrich:

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

Ich musste dasselbe bei mehreren Eingaben machen, also aktualisierte ich die @Endy Tjahjono-Methode .

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

      }
    }
  });
2
Hugo

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

2
coder000001

Ich musste die Direktive von Endy ändern, um Last Modified, lastModifiedDate, Name, Größe, Typ und Daten sowie ein Array von Dateien zu erhalten. Für diejenigen von Ihnen, die diese zusätzlichen Funktionen benötigt haben, hier.

UPDATE: Ich habe einen Fehler gefunden. Wenn Sie die Datei (en) auswählen und dann erneut auswählen, aber abbrechen, werden die Dateien nie abgewählt. Also habe ich meinen Code aktualisiert, um das zu beheben.

 .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
Parley Hammon

Wenn Sie etwas eleganter/integrierter haben möchten, können Sie einen decorator verwenden, um die input-Direktive um Unterstützung für type=file zu erweitern. Beachten Sie vor allem, dass diese Methode in IE9 nicht funktioniert, da IE9 die Datei-API nicht implementiert hat. Die Verwendung von JavaScript für das Hochladen von Binärdaten unabhängig vom Typ über XHR ist nativ in IE9 oder früher nicht möglich (die Verwendung von ActiveXObject für den Zugriff auf das lokale Dateisystem zählt nicht, da bei der Verwendung von ActiveX nur Sicherheitslücken erforderlich sind).

Für diese genaue Methode ist auch AngularJS 1.4.x oder höher erforderlich. Möglicherweise können Sie diese Einstellung jedoch anpassen, um $provide.decorator anstelle von angular.Module.decorator zu verwenden. Ich habe diese Gist geschrieben, um zu zeigen, wie dies mit John Papas AngularJS-Stil durchgeführt wird führen :

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

Warum wurde das überhaupt nicht gemacht? Die Unterstützung von AngularJS soll nur bis zum IE9 reichen. Wenn Sie mit dieser Entscheidung nicht einverstanden sind und der Meinung sind, sie hätten dies sowieso nur tun sollen, dann sollten Sie den Wagen auf Angular 2+ setzen, da Angular 2 eine bessere moderne Unterstützung ist.

Das Problem ist (wie bereits erwähnt), dass ohne die Datei api Unterstützung, dies richtig zu machen, ist für den Kern angesichts unseres Grundlinie ist IE9 und Polyfilling dieses Zeug steht außer Frage für den Kern.

Außerdem wird versucht, diese Eingabe auf eine Weise zu behandeln, die nicht .__ ist. Cross-Browser-kompatibel macht es nur für Lösungen von Drittanbietern schwieriger, die müssen nun die Kernlösung bekämpfen/deaktivieren/umgehen.

...

Ich werde das schließen, als wir # 1236 geschlossen haben. Winkel 2 wird gebaut, um moderne Browser zu unterstützen, und mit dieser Datei wird einfach verfügbar.

0
p0lar_bear