我进行了广泛的搜索,虽然我可能错过了它,但我找不到解决这个问题的方法:使用 $resource 操作上传文件。
让我们举个例子:我们的 RESTful 服务允许我们通过向 /images/ 端点发出请求来访问图像。每个Image 都有一个标题、描述和指向图像文件的路径。使用 RESTful 服务,我们可以获取所有这些 (GET /images/)、一个 (GET /images/1) 或添加一个 (POST /images)。 Angular 允许我们使用 $resource 服务轻松完成此任务,但不允许文件上传——这是第三个操作所必需的——开箱即用(和they don't seem to be planning on supporting it anytime soon)。那么,如果它不能处理文件上传,我们将如何使用非常方便的 $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 对象的属性,但这次我使用了直接取自here 的自定义指令。该指令使得每次文件 input 的内容发生更改时,都会将 FileList 对象放入绑定属性中,而不是 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);
});
}
});
(在这种情况下,我在本地机器的 3000 端口上运行 NodeJS 服务器,响应是一个 json 对象,其中包含一个 status 字段和一个可选的 data 字段)。
为了使文件上传工作,我们只需要正确配置 $http 服务,例如在 app 对象的 .config 调用中。具体来说,我们需要将每个 post 请求的数据转换为一个 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 到 save() 对象。
警告这有一些限制:
- 它不适用于旧版浏览器。对不起:(
-
如果您的模型具有“嵌入式”文档,例如
{
title: "A title",
attributes: {
fancy: true,
colored: false,
nsfw: true
},
image: null
}
那么您需要相应地重构 transformRequest 函数。 例如,您可以JSON.stringify 嵌套对象,前提是您可以在另一端解析它们
英语不是我的主要语言,所以如果我的解释晦涩难懂,请告诉我,我会尝试改写它:)
- 这只是一个例子。您可以根据您的应用需要做什么来扩展它。
我希望这会有所帮助,干杯!
编辑:
正如@david 所指出的,侵入性较小的解决方案是仅为那些真正需要它的$resources 定义此行为,而不是转换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。