AngularJS:使用 $resource 上传文件(解决方案) [英] AngularJS: Upload files using $resource (solution)

查看:29
本文介绍了AngularJS:使用 $resource 上传文件(解决方案)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用 AngularJS 与 RESTful 网络服务交互,使用 $resource 来抽象暴露的各种实体.其中一些实体是图像,因此我需要能够使用 $resource对象"的 save 操作在同一请求中发送二进制数据和文本字段.

如何使用 AngularJS 的 $resource 服务在单个 POST 请求中发送数据并将图像上传到一个安静的网络服务?

解决方案

我进行了广泛的搜索,虽然我可能错过了,但我找不到解决此问题的方法:使用 $resource 上传文件行动.

让我们举个例子:我们的 RESTful 服务允许我们通过向 /images/ 端点发出请求来访问图像.每个Image 都有一个标题、一个描述和指向图像文件的路径.使用 RESTful 服务,我们可以获取所有 (GET/images/)、单个 (GET/images/1) 或添加一个 (POST/图像).Angular 允许我们使用 $resource 服务轻松完成此任务,但不允许文件上传 - 这是第三个操作所必需的 - 开箱即用(和 他们似乎不打算很快支持它).那么,如果它不能处理文件上传,我们将如何使用非常方便的 $resource 服务?事实证明这很容易!

我们将使用数据绑定,因为它是 AngularJS 最棒的特性之一.我们有以下 HTML 表单:

<div class="form-group"><input class="form-control" ng-model="newImage.title" placeholder="Title" required>

<div class="form-group"><input class="form-control" ng-model="newImage.description" placeholder="Description">

<div class="form-group"><input type="file" files-model="newImage.image" required >

<div class="form-group clearfix"><button class="btn btn-success pull-right" type="submit" ng-disabled="form.$invalid">保存</button>

</表单>

如您所见,有两个文本 input 字段,每个字段都绑定到单个对象的一个​​属性,我将其称为 newImage.文件 input 也绑定到 newImage 对象的属性,但这次我使用了直接从 这里.这个指令使得每次文件 input 的内容改变时,一个 FileList 对象被放入绑定属性而不是一个 fakepath(这将是 Angular 的标准行为).

我们的控制器代码如下:

angular.module('clientApp').controller('MainCtrl', function ($scope, $resource) {var Image = $resource('http://localhost:3000/images/:id', {id: "@_id"});Image.get(函数(结果){if (result.status != 'OK')抛出 result.status;$scope.images = result.data;})$scope.newImage = {};$scope.submit = function() {Image.save($scope.newImage, function(result) {if (result.status != 'OK')抛出 result.status;$scope.images.push(result.data);});}});

(在本例中,我在本地机器上的 3000 端口上运行 NodeJS 服务器,响应是一个包含 status 字段和可选 data 字段的 json 对象字段).

为了使文件上传工作,我们只需要正确配置 $http 服务,例如在 app 对象上的 .config 调用中.具体来说,我们需要将每个 post 请求的数据转换为一个 FormData 对象,以便以正确的格式发送到服务器:

angular.module('clientApp', ['ngCookies','ngResource','ngSanitize','ngRoute']).config(function ($httpProvider) {$httpProvider.defaults.transformRequest = 函数(数据){如果(数据 === 未定义)返回数据;var fd = new FormData();angular.forEach(数据,函数(值,键){如果(文件列表的值实例){if (value.length == 1) {fd.append(key, value[0]);} 别的 {angular.forEach(值,函数(文件,索引){fd.append(key + '_' + index, file);});}} 别的 {fd.append(key, value);}});返回 fd;}$httpProvider.defaults.headers.post['Content-Type'] = undefined;});

Content-Type 标头设置为 undefined 因为手动将其设置为 multipart/form-data 不会设置边界值,并且服务器将无法正确解析请求.

就是这样.现在您可以使用 $resourcesave() 包含标准数据字段和文件的对象.

警告这有一些限制:

  1. 它不适用于旧版浏览器.对不起:(
  2. 如果您的模型具有嵌入式"文档,例如

    <代码>{title: "一个标题",属性: {幻想:真实,有色:假,nsfw:是的},图像:空}

    那么你需要相应地重构transformRequest函数.例如,您可以 JSON.stringify 嵌套对象,前提是您可以在另一端解析它们

  3. 英语不是我的主要语言,所以如果我的解释晦涩难懂,请告诉我,我会尝试改写:)

  4. 这只是一个例子.您可以根据您的应用程序需要执行的操作对此进行扩展.

我希望这会有所帮助,加油!

正如 @david 所指出的,一种侵入性较小的解决方案是仅为那些 定义此行为$resources 实际需要它,而不是转换 AngularJS 发出的每个请求.您可以通过像这样创建 $resource 来做到这一点:

$resource('http://localhost:3000/images/:id', {id: "@_id"}, {节省: {方法:'POST',transformRequest: '<上面定义的转换方法>',标题:'<见下文>'}});

至于标题,您应该创建一个满足您要求的标题.您唯一需要指定的是 'Content-Type' 属性,方法是将其设置为 undefined.

I'm using AngularJS to interact with a RESTful webservice, using $resource to abstract the various entities exposed. Some of this entities are images, so I need to be able to use the save action of $resource "object" to send both binary data and text fields within the same request.

How can I use AngularJS's $resource service to send data and upload images to a restful webservice in a single POST request?

解决方案

I've searched far and wide and, while I might have missed it, I couldn't find a solution for this problem: uploading files using a $resource action.

Let's make this example: our RESTful service allows us to access images by making requests to the /images/ endpoint. Each Image has a title, a description and the path pointing to the image file. Using the RESTful service, we can get all of them (GET /images/), a single one (GET /images/1) or add one (POST /images). Angular allows us to use the $resource service to accomplish this task easily, but doesn't allow for file uploading - which is required for the third action - out of the box (and they don't seem to be planning on supporting it anytime soon). How, then, would we go about using the very handy $resource service if it can't handle file uploads? It turns out it's quite easy!

We are going to use data binding, because it's one of the awesome features of AngularJS. We have the following HTML form:

<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>

As you can see, there are two text input fields that are binded each to a property of a single object, which I have called newImage. The file input is binded as well to a property of the newImage object, but this time I've used a custom directive taken straight from here. This directive makes it so that every time the content of the file input changes, a FileList object is put inside the binded property instead of a fakepath (which would be Angular's standard behavior).

Our controller code is the following:

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);
        });
    }
}); 

(In this case I am running a NodeJS server on my local machine on port 3000, and the response is a json object containing a status field and an optional data field).

In order for the file upload to work, we just need to properly configure the $http service, for example within the .config call on the app object. Specifically, we need to transform the data of each post request to a FormData object, so that it's sent to the server in the correct format:

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;
});

The Content-Type header is set to undefined because setting it manually to multipart/form-data would not set the boundary value, and the server would not be able to parse the request correctly.

That's it. Now you can use $resource to save() objects containing both standard data fields and files.

WARNING This has some limitations:

  1. It doesn't work on older browsers. Sorry :(
  2. If your model has "embedded" documents, like

    { title: "A title", attributes: { fancy: true, colored: false, nsfw: true }, image: null }

    then you need to refactor the transformRequest function accordingly. You could, for example, JSON.stringify the nested objects, provided you can parse them on the other end

  3. English is not my main language, so if my explanation is obscure tell me and I'll try to rephrase it :)

  4. This is just an example. You can expand on this depending on what your application needs to do.

I hope this helps, cheers!

EDIT:

As pointed out by @david, a less invasive solution would be to define this behavior only for those $resources that actually need it, and not to transform each and every request made by AngularJS. You can do that by creating your $resource like this:

$resource('http://localhost:3000/images/:id', {id: "@_id"}, { 
    save: { 
        method: 'POST', 
        transformRequest: '<THE TRANSFORMATION METHOD DEFINED ABOVE>', 
        headers: '<SEE BELOW>' 
    } 
});

As for the header, you should create one that satisfies your requirements. The only thing you need to specify is the 'Content-Type' property by setting it to undefined.

这篇关于AngularJS:使用 $resource 上传文件(解决方案)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆