`<input type =“ file” />`のng-model(DEMOディレクティブを使用)


281

タイプファイルの入力タグでng-modelを使用しようとしました:

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

しかし、コントローラーでファイルを選択した後、$ scope.vm.uploadmeはまだ未定義です。

コントローラーで選択したファイルを取得するにはどうすればよいですか?



ngModelを使用するときは、常にhtml要素のnameプロパティを指定する必要があると思います。
Sam

回答:


327

ディレクティブで回避策を作成しました:

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

そして、入力タグは次のようになります。

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

または、ファイル定義だけが必要な場合:

.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
imgタグでuploadmeをsrcとして使用しているため、ディレクティブによって設定されていることがわかります。ただし、$ scope.uploadmeを使用してコントローラーから取得しようとすると、「未定義」です。ただし、コントローラからuploadmeを設定できます。たとえば、$ scope.uploadme = "*"は画像を非表示にします。
Quested Aronssonによる2013

5
問題は、ディレクティブがchildScopeを作成し、uploadmeをそのスコープに設定することです。元の(親)スコープにもuploadmeがあり、childScopeの影響を受けません。HTMLのuploadmeはどちらのスコープからでも更新できます。childScopeをまったく作成しないようにする方法はありますか?
Quested Aronssonによる

4
@AlexCよく質問はファイルのアップロードではなく、ng-modelが機能しないことでした:)当時、私はファイルをアップロードする必要はありませんでした。しかし最近、このegghead.ioチュートリアルからファイルをアップロードする方法を学びました。
Endy Tjahjono、2015

10
$ scope。$ on( '$ destory'、function(){element.unbind( "change");}を忘れないでください
Nawlbergs

2
質問があります...この方法は、プレーンなJavaScriptやHTMLに比べて複雑すぎませんか?真剣に、あなたは本当にこのソリューションに到達するためにAngularJSを理解する必要があります...そして、私はjavascriptイベントで同じことをすることができるようです。なぜそれを単純なJSの方法ではなくAngularの方法で行うのですか?
AFP_555 2017年

53

私はこのディレクティブを使用します:

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

次のように呼び出します。

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

プロパティ(editItem.editItem._attachments_uri.image)には、選択したファイルの内容がデータURI(!)として入力されます。

このスクリプトは何もアップロードしないことに注意してください。ファイルにエンコードされた広告a data-uri(base64)のコンテンツをモデルに入力するだけです。

ここで実際のデモを確認してください:http : //plnkr.co/CMiHKv2BEidM9SShm9Vv


4
有望に見えますが、コードの背後にあるロジックを説明し、ブラウザーの互換性(IEと非fileAPIブラウザーがほとんど)についてコメントしていただけますか?
Oleg Belousov 2013年

また、私の理解の及ぶ限り、AJAXリクエストのcontent-typeヘッダーをundefinedに設定し、そのようなフィールドをサーバーに送信しようとすると、ブラウザーがfileAPIをサポートしていると仮定して、angularはそれをアップロードします。正解ですか?
Oleg Belousov 2013年

@OlegTikhonovあなたは正しくない!このスクリプトは何も送信しません。選択したファイルをBase64文字列として読み取り、モデルをその文字列で更新します。
Elmerは

@Elmerはい、わかりました。つまり、ファイルフィールド(FileAPIオブジェクト内のユーザーのマシンにあるファイルへの相対パス)を含むフォームを送信することにより、XHRリクエストによってファイルを角度アップロードすることができますコンテンツタイプヘッダーを未定義に設定する
Oleg Belousov '15

1
ngModel$render関数を上書きする目的は何ですか?
sp00m 2016

39

有効にする方法<input type="file">で動作するようにng-model

動作するディレクティブの動作デモ ng-model

コアng-modelディレクティブは<input type="file">、そのままでは機能しません。

このカスタムディレクティブは有効ng-modelと有効化できるという利点があるng-changeng-requiredng-formして仕事にディレクティブを<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>


条件を使用して、選択したファイルがないかどうかを確認できます。ng-modelは未定義です**** if(files.length> 0){ngModel。$ setViewValue(files); } else {ngModel。$ setViewValue(undefined); }
Farshad Kazemi 2017

ファイルのデータを取得するには?そして、{{file.name}}のように使用できる他の属性は何ですか
Adarsh Singh


26

これは、@ endy-tjahjonoのソリューションの補足です。

スコープからuploadmeの値を取得できなくなりました。にもかかわらずuploadme目に見えディレクティブによって更新されたHTMLで、私はまだ$ scope.uploadmeことによって、その値にアクセスできませんでした。しかし、スコープからその値を設定することができました。神秘的でしょ?

結局のところ、ディレクティブによって子スコープが作成され、子スコープには独自のuploadmeがありました

解決策は、uploadmeの値を保持するために、プリミティブではなくオブジェクトを使用することでした

私が持っているコントローラーでは:

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

そしてHTMLで:

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

ディレクティブに変更はありません。

これで、すべて期待どおりに動作します。$ scope.uploadmeを使用して、コントローラーからuploadme.srcの値を取得できます。


1
うん、それだけです。
Quested Aronssonによる2013

1
私は同じ経験を確認します。とても素敵なデバッグと説明。ディレクティブが独自のスコープを作成する理由がわかりません。
Adam Casey

または、インライン宣言:$scope.uploadme = { src: '' }
maxathousand

9

こんにちはみんな私はディレクティブを作成し、亭に登録しました。

このlibは、入力ファイルのモデリングに役立ちます。ファイルデータだけでなく、ファイルdataurlまたはbase 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/

願っています


$ scopeを使用してそれを使用する方法?これを使用してみましたが、デバッグ時に未定義になりました。
グジャラートサンタナ

1
いい仕事よざわらたま!それはうまくいきます。@GujaratSantana、<input type = "file" ng-file-model = "myDocument" />の場合、$ scope.myDocument.nameまたは一般的に$ scope.myDocument。<any property>を使用します。ここで、プロパティは["lastModified"、 "lastModifiedDate"、 "name"、 "size"、 "type"、 "data"]
Jay Dharmendra Solanki


複数のファイルをアップロードする方法
aldrien.h 2017

このreader.readAsDataURLメソッドは廃止されました。最新のコードはURL.create ObjectURL()を使用しています。
georgeawg

4

これは少し変更されたバージョンであり、ng-modelの使用法と同じように、スコープ内の属性の名前を指定できます。

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

指令:

.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またはアンダースコアを使用して複数のファイルを入力する場合:

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

ファイルオブジェクトを返すことに対する称賛。それをDataURLに変換する他のディレクティブは、コントローラーがファイルをアップロードすることを困難にします。
georgeawg

2

複数の入力で同じことをしなければならなかったので、@ Endy Tjahjonoメソッドを更新しました。読み込まれたすべてのファイルを含む配列を返します。

  .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

Endyのディレクティブを変更して、Last Modified、lastModifiedDate、名前、サイズ、タイプ、およびデータを取得したり、ファイルの配列を取得したりできるようにする必要がありました。これらの追加機能が必要な方のために、ここに進みます。

更新:ファイルを選択してからもう一度選択して、代わりにキャンセルすると、表示されるようにファイルの選択が解除されないというバグを発見しました。それを修正するためにコードを更新しました。

 .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

もう少しエレガントで統合されたものが必要な場合は、デコレータを使用して、inputディレクティブををサポートするように拡張できますtype=fileIE9はファイルAPIを実装していないため、このメソッドはIE9では機能しないことに注意してください。JavaScriptを使用して、タイプに関係なくXHR経由でバイナリデータをアップロードすることは、IE9以前ではネイティブでは不可能です(ActiveXObjectローカルファイルシステムへのアクセスにActiveXを使用することは、セキュリティ上の問題を引き起こすだけなので、カウントされません)。

この正確な方法には、AngularJS 1.4.x以降も必要ですが、これを使用する$provide.decorator代わりに使用できる可能性があります。この要点はJohn PapaのAngularJSスタイルガイドに準拠しながら行う方法を示すためangular.Module.decoratorに書きました。

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

なぜこれが最初に行われなかったのですか?AngularJSのサポートは、IE9にまでさかのぼることを目的としています。あなたがこの決定に同意せず、彼らがとにかくこれを入れるべきだったと思うなら、より良いサポートが文字通りAngular 2が存在する理由であるAngular 2+にワゴンを飛び越えてください。

(前述したように)ファイルapiサポートなしでこれを適切に実行することは、ベースラインがIE9であることを考えるとコアにとって実現不可能であり、このものをポリフィルすることはコアの問題ではありません。

さらに、クロスブラウザー互換性のない方法でこの入力を処理しようとすると、コアソリューションと戦う/無効にする/回避する必要があるサードパーティソリューションの場合のみ困難になります。

...

#1236を閉じたときと同じように、これを閉じます。Angular 2は、最新のブラウザーをサポートするために構築されており、そのファイルを使用すると簡単に利用できます。


-2

これを試してください、これは角度のある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);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.