AngularJS:使用$ resource上传文件(解决方案)

发布于 2021-02-01 10:35:42

我正在使用AngularJS与RESTfulWeb服务进行交互,$resource用于提取公开的各种实体。其中一些实体是图像,因此我需要能够使用“对象”
save动作$resource在同一请求中发送二进制数据和文本字段。

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

关注者
0
被浏览
324
1 个回答
  • 面试哥
    面试哥 2021-02-01
    为面试而生,有面试问题,就找面试哥。

    我进行了无数次搜索,尽管可能会错过它,但找不到该问题的解决方案:使用$ 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属性中,而不是a
    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服务器,并且响应是一个包含一个status字段和一个可选data字段的json对象)。

    为了使文件上传正常工作,我们只需要正确配置$http服务,例如在app对象上的.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将未设定边界值,并且服务器将不能正确解析该请求。

    而已。现在你可以使用$resourcesave()含有标准的数据字段和文件的对象。

    警告 这有一些限制:

    1. 在较旧的浏览器上不起作用。对不起:(
    2. 如果您的模型具有“嵌入式”文档,例如

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

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

    1. 英语不是我的主要语言,所以如果我的解释不清楚,请告诉我,我将尝试重新措辞:)

    2. 这只是一个例子。您可以根据您的应用程序需要做什么来扩展它。

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

    编辑:

    正如@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



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看