`<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 = "*"는 이미지를 사라지게합니다.
퀘스트 당 Aronsson

5
문제는 지시문이 childScope를 만들고 해당 범위에서 uploadme을 설정한다는 것입니다. 원래 (부모) 범위에는 uploadme가 있으며 이는 자식 범위의 영향을받지 않습니다. 어느 범위에서나 HTML의 uploadme를 업데이트 할 수 있습니다. childScope를 전혀 작성하지 않는 방법이 있습니까?
퀘스트 당 Aronsson

4
@ AlexC 음 문제는 파일을 업로드하는 것이 아니라 작동하지 않는 ng-model에 관한 것입니다. :) 당시에는 파일을 업로드 할 필요가 없었습니다. 그러나 최근 에이 egghead.io 튜토리얼 에서 파일을 업로드하는 방법을 배웠습니다 .
Endy Tjahjono

10
잊지 말고 $ scope. $ on ( '$ destory', function () {element.unbind ( "change");}
Nawlbergs

2
질문이 있습니다 ....이 방법은 일반 자바 스크립트 및 HTML에 비해 너무 복잡하지 않습니까? 진지하게, 당신은이 솔루션에 도달하기 위해 AngularJS를 이해해야합니다 ... 그리고 자바 스크립트 이벤트로도 똑같이 할 수있는 것 같습니다. 왜 일반적인 JS 방식이 아닌 Angular 방식입니까?
AFP_555

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 (!)로 선택한 파일의 내용으로 채워집니다.

이 스크립트는 아무 것도 업로드하지 않습니다. 그것은 데이터 인코딩 된 (base64) 파일 인코딩 된 광고의 내용으로 모델을 채 웁니다.

실제 데모를 확인하십시오 : http://plnkr.co/CMiHKv2BEidM9SShm9Vv


4
유망 해 보이며 코드의 논리를 설명하고 브라우저 호환성 (IE 및 비 fileAPI 브라우저)에 대해 의견을 말해주십시오.
Oleg Belousov

또한 이해를 돕기 위해 AJAX 요청의 컨텐츠 유형 헤더를 정의되지 않은 상태로 설정하고 이러한 필드를 서버에 보내려고 시도하면 브라우저가 fileAPI를 지원한다고 가정하고 각도가 업로드됩니다. 맞습니까?
Oleg Belousov

@OlegTikhonov 당신이 정확하지 않습니다! 이 스크립트는 아무것도 보내지 않습니다. Base64 문자열로 선택한 파일을 읽고 해당 문자열로 모델을 업데이트합니다.
엘머

@Elmer 예, 이해합니다. 즉, 파일 필드 (FileAPI 객체의 사용자 컴퓨터에있는 파일의 상대 경로)를 포함하는 양식을 보내면 XHR 요청으로 각도 업로드 파일을 만들 수 있습니다 컨텐츠 유형 헤더를 정의되지 않음으로 설정하여
올렉 Belousov

1
overwritting의 목적은 무엇인가 ngModel'의 $render기능은?
sp00m

39

활성화하는 방법 <input type="file">작업ng-model

작동하는 지시문 실무 데모 ng-model

핵심 ng-model지침은 작동하지 않습니다<input type="file"> .

이 사용자 정의 지시어는 가능 ng-model하고, 가능의 추가 혜택을 가지고 ng-change, ng-required그리고 ng-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 2016

파일의 데이터를 얻는 방법? 다른 속성이 무엇인지 그리고 우리는 같이 사용할 수 있습니다 {{file.name}}
ADARSH 싱


26

이것은 @ endy-tjahjono의 솔루션에 대한 부록입니다.

나는 uploadme 의 가치를 얻을 수없는 결국 그 범위에서 . 비록 uploadme 눈에 띄게 지침에 의해 업데이트 된 HTML에서, 나는 아직도 $ scope.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
그렇습니다.
퀘스트 당 Aronsson

1
나는 같은 경험을 확인한다. 아주 좋은 디버그 및 설명. 지시문이 왜 자체 범위를 작성하는지 잘 모르겠습니다.
Adam Casey

또는 인라인 선언 :$scope.uploadme = { src: '' }
maxathousand

9

안녕하세요 여러분 지시를 만들고 bower에 등록했습니다.

이 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
좋은 일 yozawiratama! 잘 작동합니다. 그리고 @GujaratSantana, <input type = "file"ng-file-model = "myDocument"/> 인 경우 $ scope.myDocument.name 또는 일반적으로 $ scope.myDocument. <any property>를 사용하십시오. 여기서 property는 [ "lastModified", "lastModifiedDate", "name", "size", "type", "data"]
Jay Dharmendra Solanki

bower를 통해 설치할 수 없음
Pimgd

여러 파일 업로드를 사용하는 방법?
aldrien.h 2012

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=file. 명심해야 할 주된주의 사항은 IE9가 File 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 이고이 내용을 polyfilling하면 코어에 적합하지 않다는 것입니다.

또한 브라우저 간 호환되지 않는 방식으로이 입력을 처리하려고하면 타사 솔루션의 경우 더욱 어려워 져서 핵심 솔루션과 싸우거나 비활성화 / 해결해야합니다.

...

우리가 # 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.