ng-модель для `<type type =" file "/>` (з директивою DEMO)


281

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

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

Але після вибору файлу в контролері $ range.vm.uploadme залишається невизначеним.

Як отримати вибраний файл у своєму контролері?


3
Див stackoverflow.com/a/17923521/135114 , особливо цитований приклад онлайн на jsfiddle.net/danielzen/utp7j
Дарина

Я вважаю, що вам завжди потрібно вказати властивість імені для елемента html при використанні ngModel.
Сем

Відповіді:


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
Я використовую uploadme як src у тезі img, тому я можу бачити, що він встановлюється директивою. Однак якщо я спробую схопити його з контролера за допомогою $ range.uploadme, він буде "невизначений". Я можу встановити uploadme від контролера. Наприклад, $ range.uploadme = "*" змушує зображення зникати.
Запитаний Аронсон

5
Проблема полягає в тому, що директива створює childScope і встановлює uploadme в цій області. Оригінальний (батьківський) діапазон також має файл завантаження, на який не впливає дочірня програма. Я можу оновити файл завантаження в HTML з будь-якої області. Чи є спосіб взагалі уникнути створення дитини?
Запитаний Аронсон

4
@AlexC добре було питання про те, що ng-модель не працює, не про завантаження файлів :) На той момент мені не потрібно було завантажувати файл. Але нещодавно я дізнався, як завантажувати файл із цього підручника egghead.io .
Endy Tjahjono

10
не забудьте $ range. $ on ('$ destory', function () {element.unbind ("змінити");}
Nawlbergs

2
У мене є питання… Чи не такий спосіб занадто складний порівняно із звичайним javascript та html? Серйозно, вам дійсно потрібно зрозуміти AngularJS, щоб досягти цього рішення ... і, здається, я міг би зробити те ж саме з подією javascript. Навіщо робити це кутовим шляхом, а не простим способом JS?
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 (!).

Зверніть увагу, що цей сценарій нічого не завантажує. Він заповнить вашу модель лише вмістом вашого файлу, кодованим рекламою, і data-uri (base64).

Ознайомтеся з робочою демонстрацією тут: http://plnkr.co/CMiHKv2BEidM9SShm9Vv


4
Погляньте багатообіцяючим, чи можете ви пояснити логіку коду та прокоментувати сумісність браузера (здебільшого IE та браузер без файлівAPI)?
Олег Білоусов

Крім того, наскільки я розумію, якщо я встановлю заголовок типу вмісту запиту AJAX невизначеним, і спробую надіслати таке поле на сервер, кутовий буде завантажувати його, припускаючи, що браузер підтримує fileAPI, я Я виправляю?
Олег Білоусов

@OlegTikhonov ви неправі! Цей сценарій нічого не надсилатиме. Він прочитає файл, який ви вибрали як рядок Base64, і оновить вашу модель цією рядком.
Elmer

@Elmer Так, я розумію, що я маю на увазі, що надсилаючи форму, яка містить поле файлу (відносний шлях до файлу в машині користувача в об’єкті FileAPI), ви можете зробити кутове завантаження файлу за допомогою запиту XHR встановивши заголовок типу вмісту на невизначений
Олег Білоусов

1
Яка мета overwritting ngModel«s $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-модель не буде визначена ****, якщо (files.length> 0) {ngModel. $ SetViewValue (файли); } else {ngModel. $ setViewValue (не визначено); }
Фаршад Каземі

Як отримати дані про файл? І які ще атрибути ми можемо використовувати на зразок {{file.name}}
Adarsh ​​Singh


26

Це доповнення до рішення @ endy-tjahjono.

Я не змогла отримати значення uploadme з області. Навіть через uploadme в HTML був помітно оновлений директивою, я все одно не міг отримати доступ до його значення за $ length.uploadme. Мені вдалося встановити його значення із сфери застосування. Таємничий, правда ..?

Як виявилося, директива була створена для дочірньої області, і область для дітей мала своє власне завантаження .

Рішення полягало у використанні об'єкта, а не примітиву, щоб утримувати значення uploadme .

У контролері я:

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

та в HTML:

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

Зміни в директиві відсутні.

Тепер все працює як очікувалося. Я можу схопити значення uploadme.src з мого контролера за допомогою $ range.uploadme.


1
Так, саме так.
Запитаний Аронсон

1
Я підтверджую той же досвід; дуже приємна налагодження та пояснення. Я не впевнений, чому директива створює власну сферу застосування.
Адам Кейсі

В якості альтернативи вбудована декларація:$scope.uploadme = { src: '' }
maxathousand

9

Привіт, хлопці, я створюю директиву та зареєстровані на bower.

ця вкладка допоможе вам моделювати вхідний файл не тільки повертати дані файлу, але й файл 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/

Надія допоможе вам


як використовувати його за допомогою $ range? Я спробував це скористатися, але я не визначив при налагодженні.
Гуджарат Сантана

1
Приємної роботи йозавіратами! Це добре працює. І @GujaratSantana, якщо <input type = "file" ng-file-model = "myDocument" /> тоді просто використовуйте $ range.myDocument.name або загалом $ names.myDocument. <Будь-яке властивість>, де властивість є однією з ["lastModified", "lastModifiedDate", "name", "size", "type", "data"]
Джей Дхармендра Соланкі

не можна встановити через bower
Pimgd

як користувач для завантаження декількох файлів?
aldrien.h

reader.readAsDataURLМетод є застарілим. Сучасний код використовує URL.create ObjectURL () .
georgeawg

4

Це дещо змінена версія, яка дозволяє вказати ім'я атрибута в області застосування, як і в ng-моделі, використання:

    <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, оскільки IE9 не реалізує файловий 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 будується для підтримки сучасних браузерів, і підтримка цих файлів буде легко доступною.


-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);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.