AngularJS: как реализовать простую загрузку файлов с многочастной формой?

144

Я хочу сделать простую составную публикацию формы из AngularJS на сервер node.js, форма должна содержать объект JSON в одной части и изображение в другой части (в настоящее время я публикую только объект JSON с $ resource)

Я решил начать с ввода type = "file", но потом обнаружил, что AngularJS не может с этим связаться ...

Все примеры, которые я могу найти, предназначены для переноса плагинов jQuery для drag & drop. Я хочу простую загрузку одного файла.

Я новичок в AngularJS и совсем не чувствую себя комфортно при написании своих собственных директив.

Гал Бен-Хаим
источник
1
я думаю , что это может помочь: noypi-linux.blogspot.com/2013/04/...
Noypi Gilas
1
Смотрите этот ответ: stackoverflow.com/questions/18571001/… Множество вариантов там для уже работающих систем.
Анойз

Ответы:

188

Реально работающее решение без каких-либо зависимостей, кроме angularjs (протестировано с v.1.0.6)

HTML

<input type="file" name="file" onchange="angular.element(this).scope().uploadFile(this.files)"/>

Angularjs (1.0.6) не поддерживает ng-модель для тегов «input-file», поэтому вы должны сделать это «родным способом», который передает все (в конечном итоге) выбранные файлы от пользователя.

контроллер

$scope.uploadFile = function(files) {
    var fd = new FormData();
    //Take the first selected file
    fd.append("file", files[0]);

    $http.post(uploadUrl, fd, {
        withCredentials: true,
        headers: {'Content-Type': undefined },
        transformRequest: angular.identity
    }).success( ...all right!... ).error( ..damn!... );

};

Крутой частью является неопределенный тип контента и transformRequest: angular.identity, которые дают $ http возможность выбрать правильный «тип контента» и управлять границей, необходимой при обработке многокомпонентных данных.

Фабио Бонфанте
источник
2
@Fabio Можете ли вы объяснить мне, что делает этот transformRequest? что такое angular.dentity? Я бился головой весь день, чтобы выполнить загрузку нескольких файлов.
agpt
1
@RandomUser в java-приложении для отдыха, что-то в этом роде mkyong.com/webservices/jax-rs/file-upload-example-in-jersey
Фабио Бонфанте,
2
вау, просто великолепно, спасибо большое. Здесь я должен загрузить несколько изображений и некоторые другие данные, так что я манипулирую fd asfd.append("file", files[0]); fd.append("file",files[1]); fd.append("name", "MyName")
Moshii
1
console.log (fd) показывает, что форма пуста? это так?
Дж Борн
4
Обратите внимание, что это не будет работать, если у вас отключена функция debugInfo (как рекомендовано)
Bruno Peres
42

Вы можете использовать простую / облегченную директиву ng-file-upload . Он поддерживает перетаскивание, прогресс файла и загрузку файлов для браузеров, отличных от HTML5, с помощью FlashAP-приложения FileAPI

<div ng-controller="MyCtrl">
  <input type="file" ngf-select="onFileSelect($files)" multiple>
</div>

JS:

//inject angular file upload directive.
angular.module('myApp', ['ngFileUpload']);

var MyCtrl = [ '$scope', 'Upload', function($scope, Upload) {
  $scope.onFileSelect = function($files) {
  Upload.upload({
    url: 'my/upload/url',
    file: $files,            
  }).progress(function(e) {
  }).then(function(data, status, headers, config) {
    // file is uploaded successfully
    console.log(data);
  }); 

}];
Даниал
источник
Разве это не выполняет один запрос POST для каждого файла за раз?
Анойз
Да, это так. В системе отслеживания проблем GitHub есть обсуждения, почему лучше загружать файлы один за другим. Flash API не поддерживает совместную отправку файлов, а AFAIK Amazon S3 также не поддерживает его.
2013 г.
Итак, вы говорите, что общий более правильный подход - отправка одного файла POST-запроса за раз? Я вижу в этом несколько преимуществ, но также больше проблем при обеспечении спокойной поддержки на стороне сервера.
Анойз
2
Я реализую его, загружая каждый файл в качестве сохранения ресурса, и сервер сохраняет его в локальной файловой системе (или базе данных) и возвращает уникальный идентификатор (то есть случайную папку / имя файла или идентификатор базы данных) для этого файла. Затем, когда все загрузки завершены, клиент отправляет еще один запрос PUT / POST, в котором указаны дополнительные данные и идентификаторы файлов, загруженных для этого запроса. Затем сервер сохранит запись со связанными файлами. Это похоже на Gmail, когда вы загружаете файлы, а затем отправляете электронное письмо.
Даниал
1
Это не "простой / легкий". В разделе примеров даже нет примера загрузки только одного файла.
Крис
9

Более эффективно отправить файл напрямую.

Кодирование base64 из Content-Type: multipart/form-dataдобавляет дополнительные накладные расходы на 33%. Если сервер поддерживает это, более эффективно отправлять файлы напрямую:

$scope.upload = function(url, file) {
    var config = { headers: { 'Content-Type': undefined },
                   transformResponse: angular.identity
                 };
    return $http.post(url, file, config);
};

При отправке POST с объектом File важно установить 'Content-Type': undefined. Затем метод отправки XHR обнаружит объект File и автоматически установит тип содержимого.

Чтобы отправить несколько файлов, см. « Выполнение нескольких $http.postзапросов напрямую из списка файлов».


Я решил начать с ввода type = "file", но потом обнаружил, что AngularJS не может с этим связаться ...

<input type=file>Элемент не по умолчанию работа с директивой нг-модели . Требуется специальная директива :

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

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>
    
    <h2>Files</h2>
    <div ng-repeat="file in fileArray">
      {{file.name}}
    </div>
  </body>


$http.post с типом контента multipart/form-data

Если нужно отправить multipart/form-data:

<form role="form" enctype="multipart/form-data" name="myForm">
    <input type="text"  ng-model="fdata.UserName">
    <input type="text"  ng-model="fdata.FirstName">
    <input type="file"  select-ng-files ng-model="filesArray" multiple>
    <button type="submit" ng-click="upload()">save</button>
</form>
$scope.upload = function() {
    var fd = new FormData();
    fd.append("data", angular.toJson($scope.fdata));
    for (i=0; i<$scope.filesArray.length; i++) {
        fd.append("file"+i, $scope.filesArray[i]);
    };

    var config = { headers: {'Content-Type': undefined},
                   transformRequest: angular.identity
                 }
    return $http.post(url, fd, config);
};

При отправке POST с API FormData важно установить 'Content-Type': undefined. Затем метод отправки XHR обнаружит FormDataобъект и автоматически установит заголовок типа контента на multipart / form-data с правильной границей .

georgeawg
источник
filesInputДиректива не кажется , работает с угловыми 1.6.7, я могу видеть файлы в ng-repeatно то же $scope.variableпустая в контроллере? Также один из ваших примеров использует file-modelи одинfiles-input
Дэн
@ Я проверял это, и это работает. Если у вас возникли проблемы с вашим кодом, вам следует задать новый вопрос с примером Minimal, Complete, Verifiable . Изменено имя директивы на select-ng-files. Протестировано с AngularJS 1.7.2.
georgeawg
5

У меня только что была эта проблема. Так что есть несколько подходов. Во-первых, новые браузеры поддерживают

var formData = new FormData();

Перейдите по этой ссылке в блог с информацией о том, как поддержка ограничена современными браузерами, но в остальном это полностью решает эту проблему.

В противном случае вы можете опубликовать форму в iframe, используя целевой атрибут. При публикации формы обязательно установите целевое значение для iframe со свойством display, равным none. Цель - это имя iframe. (Просто, чтобы вы знали.)

надеюсь, это поможет

Эдгар Мартинес
источник
AFAIK FormData не работает с IE. может быть, лучше сделать кодировку base64 файла изображения и отправить его в формате JSON? Как я могу привязать к входу type = "file" с AngularJS, чтобы получить выбранный путь к файлу?
Гал Бен-Хаим
3

Я знаю, что это поздняя запись, но я создал простую директиву загрузки. Что вы можете получить работу в кратчайшие сроки!

<input type="file" multiple ng-simple-upload web-api-url="/api/post"
       callback-fn="myCallback" />

ng-simple-upload more on Github с примером использования Web API.

shammelburg
источник
2
честно говоря, предложение скопировать-вставить код в ваш проект readme может быть большой черной меткой. попробуйте интегрировать ваш проект с общими менеджерами пакетов, такими как npm или bower.
Стефано Торрези,
2

Вы можете загрузить через $resource, назначив данные атрибуту params ресурса actionsследующим образом:

$scope.uploadFile = function(files) {
    var fdata = new FormData();
    fdata.append("file", files[0]);

    $resource('api/post/:id', { id: "@id" }, {
        postWithFile: {
            method: "POST",
            data: fdata,
            transformRequest: angular.identity,
            headers: { 'Content-Type': undefined }
        }
    }).postWithFile(fdata).$promise.then(function(response){
         //successful 
    },function(error){
        //error
    });
};
Эмека Мбах
источник
1

Я только что написал простую директиву (из существующего, конечно) для простого загрузчика в AngularJs.

(Точный плагин для загрузки jQuery: https://github.com/blueimp/jQuery-File-Upload )

Простой загрузчик, использующий AngularJs (с реализацией CORS)

(Хотя серверная часть для PHP, вы можете просто изменить его узел)

sk8terboi87 ツ
источник
1
Не забудьте упомянуть, что вы не уделяете времени закрытию \ реагированию на проблемы в своем репо
Олег Белоусов,
@OlegTikhonov: Да, серьезно зациклился на некоторых других проектах. Вряд ли в состоянии сосредоточиться.
sk8terboi87 ツ