Ответ 1
Я искал по всему миру, и, хотя я, возможно, пропустил его, я не мог найти решение этой проблемы: загрузка файлов с помощью действия $resource.
Позвольте сделать этот пример: наша служба RESTful позволяет нам получать образы, обращаясь к конечной точке /images/
. Каждый Image
имеет заголовок, описание и путь, указывающий на файл изображения. Используя службу RESTful, мы можем получить все из них (GET /images/
), один (GET /images/1
) или добавить один (POST /images
). Angular позволяет нам использовать службу $resource для выполнения этой задачи легко, но не позволяет загружать файлы - что требуется для третьего действия - из коробки (и они, похоже, не планируют поддерживать его в ближайшее время). Как же тогда мы будем использовать очень удобную службу $resource, если она не сможет обрабатывать загрузки файлов? Оказывается, это довольно легко!
Мы собираемся использовать привязку данных, потому что это одна из удивительных особенностей AngularJS. У нас есть следующая форма HTML:
<form class="form" name="form" novalidate ng-submit="submit()">
<div class="form-group">
<input class="form-control" ng-model="newImage.title" placeholder="Title" required>
</div>
<div class="form-group">
<input class="form-control" ng-model="newImage.description" placeholder="Description">
</div>
<div class="form-group">
<input type="file" files-model="newImage.image" required >
</div>
<div class="form-group clearfix">
<button class="btn btn-success pull-right" type="submit" ng-disabled="form.$invalid">Save</button>
</div>
</form>
Как вы можете видеть, есть два текстовых поля input
, привязанные каждому к свойству одного объекта, который я назвал newImage
. Файл input
также привязан к свойству объекта newImage
, но на этот раз я использовал пользовательскую директиву, взятую прямо из здесь. Эта директива делает так, чтобы каждый раз, когда содержимое файла input
менялось, объект FileList помещается внутри свойства binded вместо fakepath
(что будет стандартным поведением Angular).
Наш код контроллера следующий:
angular.module('clientApp')
.controller('MainCtrl', function ($scope, $resource) {
var Image = $resource('http://localhost:3000/images/:id', {id: "@_id"});
Image.get(function(result) {
if (result.status != 'OK')
throw result.status;
$scope.images = result.data;
})
$scope.newImage = {};
$scope.submit = function() {
Image.save($scope.newImage, function(result) {
if (result.status != 'OK')
throw result.status;
$scope.images.push(result.data);
});
}
});
(В этом случае я запускаю NodeJS-сервер на моей локальной машине на порту 3000, а ответ представляет собой объект json, содержащий поле status
и необязательное поле data
).
Чтобы загрузка файла работала, нам просто нужно правильно настроить службу $http, например, в вызове .config объекта приложения. В частности, нам нужно преобразовать данные каждого почтового запроса в объект FormData, чтобы он отправлялся на сервер в правильном формате:
angular.module('clientApp', [
'ngCookies',
'ngResource',
'ngSanitize',
'ngRoute'
])
.config(function ($httpProvider) {
$httpProvider.defaults.transformRequest = function(data) {
if (data === undefined)
return data;
var fd = new FormData();
angular.forEach(data, function(value, key) {
if (value instanceof FileList) {
if (value.length == 1) {
fd.append(key, value[0]);
} else {
angular.forEach(value, function(file, index) {
fd.append(key + '_' + index, file);
});
}
} else {
fd.append(key, value);
}
});
return fd;
}
$httpProvider.defaults.headers.post['Content-Type'] = undefined;
});
Заголовок Content-Type
установлен на undefined
, поскольку установка его вручную в multipart/form-data
не будет устанавливать граничное значение, и сервер не сможет правильно разобрать запрос.
Что это. Теперь вы можете использовать объекты $resource
to save()
, содержащие как стандартные поля данных, так и файлы.
ПРЕДУПРЕЖДЕНИЕ Это имеет некоторые ограничения:
- Он не работает в старых браузерах. Извините: (
-
Если ваша модель имеет "встроенные" документы, например
{ title: "A title", attributes: { fancy: true, colored: false, nsfw: true }, image: null }
то вам необходимо реорганизовать функцию transformRequest соответствующим образом. Вы могли бы, например,
JSON.stringify
вложенные объекты, если вы можете проанализировать их на другом конце -
Английский язык не является моим основным языком, поэтому, если мое объяснение неясное, скажите мне, и я попытаюсь перефразировать его:)
- Это просто пример. Вы можете расширить его, в зависимости от того, что вам нужно делать.
Надеюсь, это поможет, ура!
EDIT:
Как указано @david, менее инвазивным решением было бы определить это поведение только для тех $resource
, которые действительно нуждаются в этом, а не для преобразования каждого и каждый запрос, сделанный AngularJS. Вы можете сделать это, создав $resource
следующим образом:
$resource('http://localhost:3000/images/:id', {id: "@_id"}, {
save: {
method: 'POST',
transformRequest: '<THE TRANSFORMATION METHOD DEFINED ABOVE>',
headers: '<SEE BELOW>'
}
});
Что касается заголовка, вы должны создать тот, который удовлетворяет вашим требованиям. Единственное, что вам нужно указать, это свойство 'Content-Type'
, установив его на undefined
.