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?

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

Elmer
quelle
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>

georgeawg
quelle
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.

Per Quested Aronsson
quelle
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

yozawiratama
quelle
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'

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

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

            }
        }
    });
Parley Hammon
quelle
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.

p0lar_bear
quelle
-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);
RRR
quelle