ng-модель для `‹ input type = file / ›` (с директивой DEMO)

Я попытался использовать ng-модель во входном теге с типом файла:

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

Но после выбора файла в контроллере $ scope.vm.uploadme все еще не определен.

Как мне получить выбранный файл в моем контроллере?


person Endy Tjahjono    schedule 12.06.2013    source источник
comment
См. stackoverflow.com/a/17923521/135114, особенно процитированный пример в Интернете по адресу jsfiddle.net/danielzen/utp7j   -  person Daryn    schedule 12.09.2013
comment
Я считаю, что вам всегда нужно указывать свойство name в элементе html при использовании ngModel.   -  person Sam    schedule 03.01.2014


Ответы (13)


Я создал обходной путь с директивой:

.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;
                });
            });
        }
    }
}]);
person Endy Tjahjono    schedule 12.06.2013
comment
Как мне получить доступ к данным изображения с контроллера? - person Per Quested Aronsson; 18.09.2013
comment
@PerQuestedAronsson через $ scope.uploadme (как в вопросе). - person Endy Tjahjono; 19.09.2013
comment
Я использую uploadme как src в теге img, поэтому я вижу, что он устанавливается директивой. Однако, если я попытаюсь получить его из контроллера с помощью $ scope.uploadme, он не определен. Однако я могу установить uploadme с контроллера. Например, $ scope.uploadme = * заставляет изображение исчезать. - person Per Quested Aronsson; 19.09.2013
comment
@PerQuestedAronsson, возможно, создаст новый вопрос с включенным образцом кода? - person Endy Tjahjono; 20.09.2013
comment
Проблема в том, что директива создает childScope и устанавливает uploadme в этой области. Исходная (родительская) область также имеет загрузку, на которую не влияет childScope. Я могу обновить uploadme в HTML из любой области. Есть ли способ вообще избежать создания childScope? - person Per Quested Aronsson; 20.09.2013
comment
@EndyTjahjono, похоже, определения файла недостаточно для его загрузки. Я что-то упускаю? - person Alex C; 17.02.2015
comment
@AlexC Ну, вопрос был о том, что ng-модель не работает, а не о загрузке файлов :) В то время мне не нужно было загружать файл. Но недавно я научился загружать файл из этого руководства egghead.io. - person Endy Tjahjono; 18.02.2015
comment
Это не работает с элементами файла с несколькими типами ввода. Есть ли способ заставить его работать? - person TechnoCrat; 17.04.2015
comment
@TechnoCrat вы можете делать разные вещи при обработке события изменения, например, получать все файлы changeEvent.target.files, а не только первый, как я. См. Две прокомментированные строки моего ответа. - person Endy Tjahjono; 17.04.2015
comment
Совет: если вы обнаружите, что ваша переменная контроллера имеет значение NULL, вам может потребоваться привязать ее к родительской области, например <input type="file" fileread="$parent.uploadme" />. Я нашел это с помощью ionic, потому что многие из его директив создают дочернюю область. Мне пришлось использовать $parent.$parent.$parent.newImage, чтобы подключиться к моему корневому контроллеру. - person Simon East; 16.10.2015
comment
не забудьте $ scope. $ on ('$ destory', function () {element.unbind (change);} - person Nawlbergs; 23.02.2016
comment
Я думаю, что лучше назначить имя файла в ng-модели по всему содержимому файла. - person LoveToCode; 07.03.2016
comment
@EndyTjahjono Мне приснился кошмар, когда я писал модульный тест для этого, любые предложения, я не могу заставить функцию изменения запускаться в модульном тесте. - person James; 10.03.2016
comment
Может ли эта директива поддерживать загрузку PDF-файлов? Поддерживает ли PDF-файл чтение dataurl? - person Jeff Tian; 18.03.2016
comment
Это отличное решение. Но не хватает одного бита. Когда ввод обновляется, форма должна быть помечена как грязная. Добавьте require: '^form', в определение директивы, передайте контроллер формы в функцию ссылки, затем вызовите form.$setDirty() в функции $apply. - person nmgeek; 16.10.2016
comment
У меня вопрос .... Разве это не слишком сложно по сравнению с обычным javascript и html? Серьезно, вам действительно нужно понять AngularJS, чтобы достичь этого решения ... и, похоже, я мог бы сделать то же самое с событием javascript. Почему это нужно делать с помощью Angular, а не простым способом JS? - person AFP_555; 21.03.2017
comment
$scope.$on('$destroy', function(){ element.unbind("change"); }); исправленный код @Nawlbergs - person Kugan Kumar; 30.11.2017
comment
чтобы проверить, очищен ли ввод, используйте: (changeEvent.target.files [0] instanceof Blob), который даст true или false, в случае false, scope.fileread = '' - person alexOtano; 25.07.2018
comment
@Endy Tjahjono Вышеупомянутое решение не работает для браузеров MacOS Firefox, Chrome и Safari. - person rajesh; 17.04.2019
comment
Метод reader.readAsDataURL устарел. Современный код использует URL.create ObjectURL () . - person georgeawg; 13.05.2019

Я использую эту директиву:

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) будет заполнено содержимым файла, который вы выбрали как data-uri (!).

Обратите внимание, что этот скрипт ничего не загружает. Он будет заполнять вашу модель только содержимым вашего файла, закодированного с объявлением data-uri (base64).

Ознакомьтесь с рабочей демонстрацией здесь: http://plnkr.co/CMiHKv2BEidM9SShm9Vv

person Elmer    schedule 01.10.2013
comment
Выглядит многообещающе, не могли бы вы объяснить логику кода и прокомментировать совместимость браузеров (в основном IE и браузер без файлового API)? - person Oleg Belousov; 06.11.2013
comment
Кроме того, насколько я понимаю, если я установлю для заголовка типа содержимого запроса AJAX значение undefined и попытаюсь отправить такое поле на сервер, angular загрузит его, предполагая, что браузер поддерживает fileAPI, am Я правильно? - person Oleg Belousov; 06.11.2013
comment
@OlegTikhonov ты не прав! Этот скрипт ничего не отправит. Он прочитает файл, который вы выбрали как строку Base64, и обновит вашу модель этой строкой. - person Elmer; 15.11.2013
comment
@Elmer Да, я понимаю, я имею в виду, что, отправив форму, содержащую поле файла (относительный путь к файлу на пользовательском компьютере в объекте FileAPI), вы можете сделать угловую загрузку файла с помощью запроса XHR установив для заголовка типа контента значение undefined - person Oleg Belousov; 16.11.2013
comment
Привет, @Elmer, это все еще не работало, но проблема заключалась в ng-repeat. Замена image in dummy.images на image in dummy.images track by $index теперь работала нормально. Благодарность - person Diego Vieira; 10.01.2014
comment
Какова цель перезаписи функции ngModel $render? - person sp00m; 06.04.2016
comment
Можно ли получить имя файла с помощью этой директивы? Загрузка работает отлично, но мне также нужны имена файлов выбранных изображений. Может кто-нибудь помочь? - person m1crdy; 03.05.2016
comment
Как можно restrict: 'A'? - person Anders Lindén; 08.11.2016
comment
Метод reader.readAsDataURL устарел. Современный код использует URL.create ObjectURL () . - person georgeawg; 13.05.2019

Как включить <input type="file"> для работы с ng-model

Рабочая демонстрация директивы, которая работает с ng-model

Директива core 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>

person georgeawg    schedule 28.03.2017
comment
Вы можете использовать условие, чтобы проверить, нет ли выбранных файлов, ng-модель не определена **** if (files.length ›0) {ngModel. $ SetViewValue (files); } еще {ngModel. $ setViewValue (не определено); } - person Farshad Kazemi; 28.06.2017
comment
Как получить данные файла? И какие еще атрибуты мы можем использовать, например {{file.name}} - person Adarsh Singh; 23.05.2019

Это дополнение к решению @ 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"/>

В директиву нет изменений.

Теперь все работает, как ожидалось. Я могу получить значение uploadme.src со своего контроллера с помощью $ scope.uploadme.

person Per Quested Aronsson    schedule 20.09.2013
comment
Ага, именно так. - person Per Quested Aronsson; 21.09.2013
comment
Подтверждаю тот же опыт; очень хорошая отладка и объяснение. Я не уверен, почему директива создает собственную область видимости. - person Adam Casey; 12.08.2014
comment
В качестве альтернативы встроенное объявление: $scope.uploadme = { src: '' } - person maxathousand; 05.09.2017

Создаю директиву и регистрируюсь на bower.

Эта библиотека поможет вам моделировать входной файл, не только возвращать данные файла, но также и dataurl файла или базу 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/

person yozawiratama    schedule 10.09.2015
comment
как использовать его с помощью $ scope? Я пробовал использовать это, но при отладке получил неопределенное значение. - person Gujarat Santana; 10.12.2015
comment
Хорошая работа, йозавиратама! Работает хорошо. И @GujaratSantana, если ‹input type = file ng-file-model = myDocument /›, тогда просто используйте $ scope.myDocument.name или вообще $ scope.myDocument. ‹Любое свойство› где свойство является одним из [lastModified, lastModifiedDate , имя, размер, тип, данные] - person Jay Dharmendra Solanki; 01.07.2016
comment
не может быть установлен через беседку - person Pimgd; 09.09.2016
comment
как пользователю для загрузки нескольких файлов? - person aldrien.h; 16.02.2017
comment
Метод reader.readAsDataURL устарел. Современный код использует URL.create ObjectURL () . - person georgeawg; 13.05.2019

Это немного измененная версия, которая позволяет вам указывать имя атрибута в области, как если бы вы это делали с 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'

    };
});
person asiop    schedule 31.07.2014

Для ввода нескольких файлов с использованием 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);
                });
            });
        }
    }
}]);
person Uelb    schedule 12.03.2015

Мне пришлось сделать то же самое при множественном вводе, поэтому я обновил метод @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] );
          }
        });

      }
    }
  });
person Hugo    schedule 08.03.2014

Мне пришлось изменить директиву Энди, чтобы я мог получать 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] );
                    }
                });

            }
        }
    });
person Parley Hammon    schedule 06.06.2017

Если вам нужно что-то более элегантное / интегрированное, вы можете использовать декоратор, чтобы расширить input директива с поддержкой type=file. Главное предостережение, о котором следует помнить, заключается в том, что этот метод не будет работать в IE9, поскольку IE9 не реализует File API. Использование JavaScript для загрузки двоичных данных независимо от типа через XHR просто невозможно изначально в IE9 или более ранних версиях (использование ActiveXObject для доступа к локальной файловой системе не считается, поскольку использование ActiveX просто вызывает проблемы с безопасностью).

Этот точный метод также требует AngularJS 1.4.x или более поздней версии, но вы можете адаптировать его для использования $provide.decorator, а не angular.Module.decorator - я написал эту суть, чтобы продемонстрировать, как это сделать в соответствии с Руководство по стилю AngularJS Джона Папы:

(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 создается для поддержки современных браузеров, и с его помощью поддержка файлов будет легко доступна.

person p0lar_bear    schedule 14.12.2018

В качестве альтернативы вы можете получить ввод и установить функцию onchange:

<input type="file" id="myFileInput" />
document.getElementById("myFileInput").onchange = function (event) {
   console.log(event.target.files);                        
};
person Ricardo Valente    schedule 03.03.2021

Попробуйте, это работает для меня в angular 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);
person RRR    schedule 15.12.2017

person    schedule
comment
Престижность за возвращение объекта file. Другие директивы, преобразующие его в DataURL, затрудняют загрузку файла контроллерами. - person georgeawg; 13.05.2019