IT이야기

AngularJS: $resource를 사용하여 파일 업로드(솔루션)

cyworld 2021. 9. 30. 21:55
반응형

AngularJS: $resource를 사용하여 파일 업로드(솔루션)


노출된 다양한 엔터티를 추상화하는 데 RESTful사용하여 웹 서비스 와 상호 작용하기 위해 AngularJS를 사용 $resource하고 있습니다. 이 엔터티 중 일부는 이미지이므로 동일한 요청 내에서 이진 데이터와 텍스트 필드를 모두 보내려면 "객체" save작업 을 사용할 수 있어야 합니다 $resource.

AngularJS의 $resource서비스를 사용 하여 단일 POST요청으로 데이터를 보내고 이미지를 편안한 웹 서비스에 업로드하려면 어떻게 해야 합니까?


나는 광범위하게 검색했고 놓쳤을 수도 있지만 $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. 파일 inputnewImage객체 의 속성에도 바인딩 되지만 이번에는 here 에서 직접 가져온 사용자 지정 지시문을 사용했습니다 . 이 지시문은 파일의 input내용이 변경 될 때마다 FileList 객체가 fakepath(Angular의 표준 동작이 되는) 대신 bind된 속성 내부에 놓 이도록 합니다 .

컨트롤러 코드는 다음과 같습니다.

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 서비스를 적절하게 구성하면 됩니다(예: 앱 개체의 .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하는 save()개체에 사용할 수 있습니다 .

경고 다음과 같은 몇 가지 제한 사항이 있습니다.

  1. 이전 브라우저에서는 작동하지 않습니다. 죄송합니다 :(
  2. 모델에 다음과 같은 문서가 "포함된" 경우

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

    그런 다음 그에 따라 transformRequest 함수를 리팩토링해야 합니다. 예를 들어 JSON.stringify다른 쪽 끝에서 구문 분석할 수 있는 경우 중첩된 개체를 사용할 수 있습니다.

  3. 영어는 제 모국어가 아니므로 설명이 불명확한 경우 다시 말씀해 주세요. :)

  4. 이것은 단지 예일 뿐입니다. 애플리케이션이 수행해야 하는 작업에 따라 이를 확장할 수 있습니다.

도움이 되었기를 바랍니다.

편집하다:

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


$resource요청 을 보내는 가장 최소한의 최소 침습 솔루션은 다음 FormData같습니다.

angular.module('app', [
    'ngResource'
])

.factory('Post', function ($resource) {
    return $resource('api/post/:id', { id: "@id" }, {
        create: {
            method: "POST",
            transformRequest: angular.identity,
            headers: { 'Content-Type': undefined }
        }
    });
})

.controller('PostCtrl', function (Post) {
    var self = this;

    this.createPost = function (data) {
        var fd = new FormData();
        for (var key in data) {
            fd.append(key, data[key]);
        }

        Post.create({}, fd).$promise.then(function (res) {
            self.newPost = res;
        }).catch(function (err) {
            self.newPostError = true;
            throw err;
        });
    };

});

이 방법은 1.4.0 이상에서 작동하지 않습니다. 자세한 내용은 AngularJS 변경 로그 (검색 $http: due to 5da1256) 및 이 문제확인하세요 . 이것은 실제로 AngularJS에서 의도하지 않은(따라서 제거된) 동작이었습니다.

양식 데이터를 FormData 개체로 변환(또는 추가)하기 위해 이 기능을 생각해 냈습니다. 서비스로 활용될 수 있을 것 같습니다.

아래 논리는 transformRequest, 또는 $httpProvider구성 내부 에 있거나 서비스로 사용될 수 있습니다. 어떤 방식으로든 Content-Type헤더는 NULL로 설정되어야 하며 그렇게 하는 것은 이 논리를 배치하는 컨텍스트에 따라 다릅니다. 예를 들어 transformRequest리소스를 구성할 때 옵션 내부 에서 다음을 수행합니다.

var headers = headersGetter();
headers['Content-Type'] = undefined;

또는 구성하는 경우 $httpProvider위의 답변에 언급된 방법을 사용할 수 있습니다.

아래 예에서 논리는 transformRequest리소스에 대한 메서드 내부에 배치 됩니다.

appServices.factory('SomeResource', ['$resource', function($resource) {
  return $resource('some_resource/:id', null, {
    'save': {
      method: 'POST',
      transformRequest: function(data, headersGetter) {
        // Here we set the Content-Type header to null.
        var headers = headersGetter();
        headers['Content-Type'] = undefined;

        // And here begins the logic which could be used somewhere else
        // as noted above.
        if (data == undefined) {
          return data;
        }

        var fd = new FormData();

        var createKey = function(_keys_, currentKey) {
          var keys = angular.copy(_keys_);
          keys.push(currentKey);
          formKey = keys.shift()

          if (keys.length) {
            formKey += "[" + keys.join("][") + "]"
          }

          return formKey;
        }

        var addToFd = function(object, keys) {
          angular.forEach(object, function(value, key) {
            var formKey = createKey(keys, key);

            if (value instanceof File) {
              fd.append(formKey, value);
            } else if (value instanceof FileList) {
              if (value.length == 1) {
                fd.append(formKey, value[0]);
              } else {
                angular.forEach(value, function(file, index) {
                  fd.append(formKey + '[' + index + ']', file);
                });
              }
            } else if (value && (typeof value == 'object' || typeof value == 'array')) {
              var _keys = angular.copy(keys);
              _keys.push(key)
              addToFd(value, _keys);
            } else {
              fd.append(formKey, value);
            }
          });
        }

        addToFd(data, []);

        return fd;
      }
    }
  })
}]);

따라서 이를 통해 문제 없이 다음을 수행할 수 있습니다.

var data = { 
  foo: "Bar",
  foobar: {
    baz: true
  },
  fooFile: someFile // instance of File or FileList
}

SomeResource.save(data);

ReferenceURL : https://stackoverflow.com/questions/21115771/angularjs-upload-files-using-resource-solution

반응형