ng-Modell für `<input type =“ file ”/>` (mit Direktive DEMO)


281

Ich habe versucht, ng-model für das Eingabe-Tag mit der Typdatei zu verwenden:

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

Nach Auswahl einer Datei im Controller ist $ scope.vm.uploadme jedoch immer noch undefiniert.

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


3
Siehe stackoverflow.com/a/17923521/135114 , insbesondere das zitierte Beispiel online unter jsfiddle.net/danielzen/utp7j
Daryn

Ich glaube, Sie müssen immer die Eigenschaft name für das HTML-Element angeben, wenn Sie ngModel verwenden.
Sam

Antworten:


327

Ich habe eine Problemumgehung mit der 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:

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

4
Ich verwende uploadme als src in einem img-Tag, damit ich sehen kann, dass es von der Direktive festgelegt wird. Wenn ich jedoch versuche, es mit $ scope.uploadme vom Controller abzurufen, ist es "undefiniert". Ich kann das Uploadme jedoch vom Controller aus einstellen. Zum Beispiel lässt $ scope.uploadme = "*" das Bild verschwinden.
Per Quested Aronsson

5
Das Problem ist, dass die Direktive ein childScope erstellt und uploadme in diesem Bereich festlegt. Der ursprüngliche (übergeordnete) Bereich verfügt auch über ein Uploadme, das vom childScope nicht beeinflusst wird. Ich kann uploadme im HTML von beiden Bereichen aus aktualisieren. Gibt es eine Möglichkeit, das Erstellen eines childScope überhaupt zu vermeiden?
Per Quested Aronsson

4
@AlexC Nun, die Frage war, ob das ng-Modell nicht funktioniert, nicht das Hochladen von Dateien :) Zu diesem Zeitpunkt musste ich die Datei nicht hochladen. Aber vor kurzem habe ich gelernt, wie man eine Datei aus diesem Ei-Tutorial hochlädt .
Endy Tjahjono

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

2
Ich habe eine Frage ... Ist das nicht zu kompliziert im Vergleich zu einfachem Javascript und HTML? Im Ernst, Sie müssen AngularJS wirklich verstehen, um zu dieser Lösung zu gelangen ... und es scheint, dass ich dasselbe mit einem Javascript-Ereignis tun könnte. Warum auf die Angular-Art und nicht auf die einfache JS-Art?
AFP_555

53

Ich benutze diese Richtlinie:

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 rufe es so auf:

<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 (!) Auswählen.

Bitte beachten Sie, dass dieses Skript nichts hochlädt. Ihr Modell wird nur mit dem Inhalt Ihrer Datei gefüllt, die ad a data-uri (base64) codiert ist.

Eine funktionierende Demo finden Sie hier: http://plnkr.co/CMiHKv2BEidM9SShm9Vv


4
Sehen Sie vielversprechend aus. Können Sie bitte die Logik hinter dem Code erläutern und Kommentare zur Browserkompatibilität abgeben (hauptsächlich IE und Nicht-FileAPI-Browser)?
Oleg Belousov

Wenn ich nach meinem besten Verständnis den Inhaltstyp-Header der AJAX-Anforderung auf undefiniert setze und versuche, ein solches Feld an den Server zu senden, lädt Angular es hoch, vorausgesetzt, der Browser unterstützt fileAPI Ich korrigiere?
Oleg Belousov

@OlegTikhonov du bist nicht korrekt! Dieses Skript sendet nichts. Es liest die Datei, die Sie als Base64-Zeichenfolge ausgewählt haben, und aktualisiert Ihr Modell mit dieser Zeichenfolge.
Elmer

@Elmer Ja, ich verstehe, was ich damit meine, ist, dass Sie durch Senden eines Formulars, das ein Dateifeld enthält (einen relativen Pfad zur Datei auf dem Computer des Benutzers in einem FileAPI-Objekt), die Datei durch eine XHR-Anforderung per Winkel hochladen können indem Sie den Inhaltstyp-Header auf undefiniert setzen
Oleg Belousov

1
Was ist der Zweck des Überschreibens ngModelder $renderFunktion?
sp00m

39

So aktivieren Sie <input type="file">die Arbeit mitng-model

Arbeitsdemo der Richtlinie, mit der gearbeitet wird ng-model

Die ng-modelKernrichtlinie funktioniert nicht <input type="file">sofort.

Diese benutzerdefinierte Richtlinie ermöglicht ng-modelund hat den zusätzlichen Vorteil ermöglicht , die ng-change, ng-requiredund ng-formRichtlinien zur Arbeit mit <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>


Sie können die Bedingung verwenden, um zu überprüfen, ob keine ausgewählten Dateien vorhanden sind. Das ng-Modell ist undefiniert. **** if (files.length> 0) {ngModel. $ SetViewValue (files); } else {ngModel. $ setViewValue (undefiniert); }
Farshad Kazemi

Wie erhalte ich Daten der Datei? Und was sind die anderen Attribute, die wir verwenden können, wie {{file.name}}
Adarsh ​​Singh


26

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

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

Wie sich herausstellte, wurde durch die Direktive ein untergeordneter Bereich erstellt, und der untergeordnete Bereich hatte ein eigenes Uploadme .

Die Lösung bestand darin, ein Objekt anstelle eines Grundelements 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 mit $ scope.uploadme von meinem Controller abrufen.


1
Ja, genau das ist es.
Per Quested Aronsson

1
Ich bestätige die gleiche Erfahrung; sehr schönes Debug und Erklärung. Ich bin mir nicht sicher, warum die Richtlinie ihren eigenen Geltungsbereich schafft.
Adam Casey

Alternativ eine Inline-Erklärung:$scope.uploadme = { src: '' }
Maxathousand

9

Hallo Leute, ich erstelle eine Direktive und habe mich bei bower registriert.

Diese Bibliothek hilft Ihnen beim Modellieren von Eingabedateien und gibt nicht nur Dateidaten zurück, sondern auch Dateidaten-URL 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


Wie benutzt man es mit $ scope? Ich habe versucht, dies zu verwenden, wurde aber beim Debuggen undefiniert.
Gujarat Santana

1
Gute Arbeit yozawiratama! Es funktioniert gut. Und @GujaratSantana, wenn <input type = "file" ng-file-model = "myDocument" />, verwenden Sie einfach $ scope.myDocument.name oder im Allgemeinen $ scope.myDocument. <Meine Eigenschaft>, wobei die Eigenschaft eine der Eigenschaften ist ["lastModified", "lastModifiedDate", "name", "size", "type", "data"]
Jay Dharmendra Solanki

kann nicht über Laube installiert werden
Pimgd

Wie kann man mehrere Dateien hochladen?
aldrien.h

Die reader.readAsDataURLMethode ist veraltet. Moderner Code verwendet URL.create ObjectURL () .
Georgiaeawg

4

Dies ist eine leicht modifizierte Version, mit der Sie den Namen des Attributs im Bereich 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'

    };
});

3

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

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

Ein großes Lob für die Rückgabe des Dateiobjekts . Die anderen Anweisungen, die es in eine DataURL konvertieren, erschweren es Controllern, die die Datei hochladen möchten.
Georgiaeawg

2

Ich musste dasselbe bei mehreren Eingaben tun, also habe ich die @ Endy Tjahjono-Methode aktualisiert. Es gibt ein Array zurück, das alle gelesenen Dateien enthält.

  .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

Ich musste die Direktive von Endy ändern, damit ich Last Modified, lastModifiedDate, Name, Größe, Typ und Daten sowie ein Array von Dateien abrufen kann. Für diejenigen unter Ihnen, die diese zusätzlichen Funktionen benötigen, geht es los.

UPDATE: Ich habe einen Fehler gefunden, bei dem die Dateien nie so abgewählt werden, wie sie angezeigt werden, wenn Sie die Datei (en) auswählen und dann erneut auswählen, aber stattdessen abbrechen. 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

Wenn Sie etwas eleganteres / integrierteres wünschen, können Sie einen Dekorateur verwenden , um die inputRichtlinie mit Unterstützung für zu erweitern type=file. Die wichtigste Einschränkung ist, dass diese Methode in IE9 nicht funktioniert, da IE9 die Datei-API nicht implementiert hat . Die Verwendung von JavaScript zum Hochladen von Binärdaten unabhängig vom Typ über XHR ist in IE9 oder früher einfach nicht nativ möglich (Verwendung vonActiveXObject für den Zugriff auf das lokale Dateisystem zählt nicht, da die Verwendung von ActiveX nur nach Sicherheitsproblemen fragt).

Für diese genaue Methode ist auch AngularJS 1.4.x oder höher erforderlich. Möglicherweise können Sie dies jedoch anpassen, $provide.decoratoranstatt es zu verwenden. angular.Module.decoratorIch habe diesen Kern geschrieben , um zu demonstrieren, wie dies unter Einhaltung des AngularJS- Styleguides von John Papa zu tun ist :

(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 AngularJS-Unterstützung soll nur bis zum IE9 zurückreichen. Wenn Sie mit dieser Entscheidung nicht einverstanden sind und der Meinung sind, dass sie dies sowieso hätten tun sollen, dann springen Sie mit dem Wagen zu Angular 2+, da Angular 2 buchstäblich eine bessere moderne Unterstützung bietet.

Das Problem ist (wie bereits erwähnt), dass dies ohne die Unterstützung der Datei-API für den Core nicht möglich ist, da unsere Basis IE9 ist und das Polyfilling dieses Materials für den Core nicht in Frage kommt.

Der Versuch, diese Eingabe auf eine Weise zu verarbeiten, die nicht browserübergreifend kompatibel ist, erschwert nur Lösungen von Drittanbietern, die nun die Kernlösung bekämpfen / deaktivieren / umgehen müssen.

...

Ich werde dies schließen, gerade als wir # 1236 geschlossen haben. Angular 2 wird entwickelt, um moderne Browser zu unterstützen, und mit dieser Datei wird die Unterstützung leicht verfügbar sein.


-2

Versuchen Sie dies, dies funktioniert für mich in eckigen 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);
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.