AngularJS: làm thế nào để thực hiện tải lên tệp đơn giản với dạng nhiều phần?


144

Tôi muốn thực hiện một bài đăng biểu mẫu nhiều phần đơn giản từ AngularJS đến máy chủ node.js, biểu mẫu phải chứa một đối tượng JSON trong một phần và một hình ảnh trong phần khác, (hiện tại tôi chỉ đăng đối tượng JSON có $ resource)

Tôi hình dung mình nên bắt đầu với kiểu đầu vào = "tập tin", nhưng sau đó phát hiện ra rằng AngularJS không thể liên kết với điều đó ..

tất cả các ví dụ tôi có thể tìm thấy là để bao bọc các plugin jQuery để kéo và thả. Tôi muốn tải lên đơn giản một tập tin.

Tôi chưa quen với AngularJS và không cảm thấy thoải mái chút nào khi viết các chỉ thị của riêng tôi.


1
tôi nghĩ rằng điều này có thể giúp: noypi-linux.blogspot.com/2013/04/ từ
Noypi Gilas

1
Xem câu trả lời này: stackoverflow.com/questions/18571001/. Rất nhiều tùy chọn ở đó cho các hệ thống đã hoạt động.
Anoyz

Câu trả lời:


188

Một giải pháp làm việc thực tế không có phụ thuộc nào khác ngoài angularjs (được thử nghiệm với v.1.0.6)

html

<input type="file" name="file" onchange="angular.element(this).scope().uploadFile(this.files)"/>

Angularjs (1.0.6) không hỗ trợ mô hình ng trên các thẻ "tệp đầu vào", do đó bạn phải thực hiện theo cách "gốc" để vượt qua tất cả (cuối cùng) các tệp đã chọn từ người dùng.

bộ điều khiển

$scope.uploadFile = function(files) {
    var fd = new FormData();
    //Take the first selected file
    fd.append("file", files[0]);

    $http.post(uploadUrl, fd, {
        withCredentials: true,
        headers: {'Content-Type': undefined },
        transformRequest: angular.identity
    }).success( ...all right!... ).error( ..damn!... );

};

Phần thú vị là loại nội dung không xác địnhbiến đổiRequest: angular.identity cung cấp cho $ http khả năng chọn đúng "loại nội dung" và quản lý ranh giới cần thiết khi xử lý dữ liệu nhiều phần.


2
@Fabio Bạn có thể giải thích cho tôi biến đổi này làm gì không? góc cạnh là gì? Tôi đã đập đầu mình suốt cả ngày chỉ để hoàn thành việc tải lên nhiều tập tin.
agpt

1
@RandomUser trong ứng dụng nghỉ ngơi java, đại loại như mkyong.com/webservice/jax-rs/file-upload-example-in-jersey
Fabio Bonfante

2
wow, chỉ là xuất sắc, cảm ơn rất nhiều. Ở đây tôi phải tải lên nhiều hình ảnh và một số dữ liệu khác để tôi thao tác với fd nhưfd.append("file", files[0]); fd.append("file",files[1]); fd.append("name", "MyName")
Moshii

1
console.log (fd) hiển thị biểu mẫu trống? có phải vậy không
J Bourne

4
Lưu ý rằng điều này sẽ không hoạt động nếu bạn đã tắt debugInfo (như được khuyến nghị)
Bruno Peres

42

Bạn có thể sử dụng lệnh ng-file-upload đơn giản / nhẹ . Nó hỗ trợ kéo và thả, tiến trình tệp và tải tệp lên cho các trình duyệt không phải là HTML5 với tệp flash shim FileAPI

<div ng-controller="MyCtrl">
  <input type="file" ngf-select="onFileSelect($files)" multiple>
</div>

JS:

//inject angular file upload directive.
angular.module('myApp', ['ngFileUpload']);

var MyCtrl = [ '$scope', 'Upload', function($scope, Upload) {
  $scope.onFileSelect = function($files) {
  Upload.upload({
    url: 'my/upload/url',
    file: $files,            
  }).progress(function(e) {
  }).then(function(data, status, headers, config) {
    // file is uploaded successfully
    console.log(data);
  }); 

}];

Đây không phải là thực hiện một yêu cầu POST cho mỗi tệp tại một thời điểm sao?
Anoyz

Có nó làm. Có các cuộc thảo luận trong trình theo dõi vấn đề github về lý do tại sao tốt hơn là tải lên từng tệp một. API Flash không hỗ trợ gửi các tệp cùng nhau và AFAIK Amazon S3 cũng không hỗ trợ.
danial

Vì vậy, bạn đang nói rằng cách tiếp cận chung hơn là gửi một yêu cầu POST một lần? Tôi có thể thấy một số lợi thế trong đó, nhưng cũng gặp nhiều rắc rối hơn khi thực hiện hỗ trợ nghỉ ngơi phía máy chủ.
Anoyz

2
Cách tôi triển khai là tải lên từng tệp dưới dạng lưu tài nguyên và máy chủ sẽ lưu nó trên hệ thống tệp cục bộ (hoặc cơ sở dữ liệu) và trả về một id duy nhất (ví dụ tên thư mục / tệp ngẫu nhiên hoặc id db) cho tệp đó. Sau đó, khi tất cả các quá trình tải lên được thực hiện, khách hàng sẽ gửi một yêu cầu PUT / POST khác để thêm dữ liệu và id của các tệp được tải lên cho yêu cầu này. Sau đó, máy chủ sẽ lưu bản ghi với các tập tin liên quan. Nó giống như gmail khi bạn tải tệp lên và sau đó gửi email.
danial

1
Đây không phải là "đơn giản / nhẹ". Phần mẫu thậm chí không có ví dụ về việc chỉ tải lên một tệp.
Chris

9

Nó là hiệu quả hơn để gửi một tập tin trực tiếp.

Các base64 mã hóa của Content-Type: multipart/form-databổ sung thêm một 33% chi phí. Nếu máy chủ hỗ trợ, việc gửi các tệp trực tiếp sẽ hiệu quả hơn:

$scope.upload = function(url, file) {
    var config = { headers: { 'Content-Type': undefined },
                   transformResponse: angular.identity
                 };
    return $http.post(url, file, config);
};

Khi gửi POST với đối tượng File , điều quan trọng là phải đặt 'Content-Type': undefined. Các phương pháp XHR gửi sau đó sẽ phát hiện các đối tượng tập tin và tự động thiết lập các loại nội dung.

Để gửi nhiều tệp, hãy xem Thực hiện nhiều yêu cầu trực tiếp từ một danh sách tệp$http.post


Tôi hình dung mình nên bắt đầu với kiểu đầu vào = "tập tin", nhưng sau đó phát hiện ra rằng AngularJS không thể liên kết với điều đó ..

Phần <input type=file>tử không mặc định hoạt động với chỉ thị ng-model . Nó cần một chỉ thị tùy chỉnh :

Bản trình diễn hoạt động của Chỉ thị "select-ng-files" hoạt động với ng-model1

angular.module("app",[]);

angular.module("app").directive("selectNgFiles", function() {
  return {
    require: "ngModel",
    link: function postLink(scope,elem,attrs,ngModel) {
      elem.on("change", function(e) {
        var files = elem[0].files;
        ngModel.$setViewValue(files);
      })
    }
  }
});
<script src="//unpkg.com/angular/angular.js"></script>
  <body ng-app="app">
    <h1>AngularJS Input `type=file` Demo</h1>
    
    <input type="file" select-ng-files ng-model="fileArray" multiple>
    
    <h2>Files</h2>
    <div ng-repeat="file in fileArray">
      {{file.name}}
    </div>
  </body>


$http.post với loại nội dung multipart/form-data

Nếu ai đó phải gửi multipart/form-data:

<form role="form" enctype="multipart/form-data" name="myForm">
    <input type="text"  ng-model="fdata.UserName">
    <input type="text"  ng-model="fdata.FirstName">
    <input type="file"  select-ng-files ng-model="filesArray" multiple>
    <button type="submit" ng-click="upload()">save</button>
</form>
$scope.upload = function() {
    var fd = new FormData();
    fd.append("data", angular.toJson($scope.fdata));
    for (i=0; i<$scope.filesArray.length; i++) {
        fd.append("file"+i, $scope.filesArray[i]);
    };

    var config = { headers: {'Content-Type': undefined},
                   transformRequest: angular.identity
                 }
    return $http.post(url, fd, config);
};

Khi gửi POST bằng API FormData , điều quan trọng là phải đặt 'Content-Type': undefined. Các phương pháp XHR gửi sau đó sẽ phát hiện các FormDatađối tượng và tự động thiết lập các tiêu đề kiểu nội dung để multipart / form-data với ranh giới thích hợp .


Lệnh filesInputnày dường như không hoạt động với Angular 1.6.7, tôi có thể thấy các tệp trong ng-repeatnhưng tương tự $scope.variablelà trống trong bộ điều khiển? Ngoài ra một trong những ví dụ của bạn sử dụng file-modelvà mộtfiles-input
Dan

@Dan tôi đã thử nó và nó hoạt động. Nếu bạn gặp vấn đề với mã của mình, bạn nên đặt câu hỏi mới với một ví dụ Tối thiểu, Hoàn chỉnh, Có thể kiểm chứng . Thay đổi tên chỉ thị thành select-ng-files. Đã thử nghiệm với AngularJS 1.7.2.
georgeawg

5

Tôi chỉ có vấn đề này. Vì vậy, có một vài cách tiếp cận. Đầu tiên là các trình duyệt mới hỗ trợ

var formData = new FormData();

Theo liên kết này đến một blog với thông tin về cách hỗ trợ được giới hạn trong các trình duyệt hiện đại nhưng nếu không thì nó hoàn toàn giải quyết được vấn đề này.

Nếu không, bạn có thể đăng biểu mẫu lên iframe bằng thuộc tính đích. Khi bạn đăng biểu mẫu, hãy chắc chắn đặt mục tiêu thành iframe với thuộc tính hiển thị của nó được đặt thành không. Mục tiêu là tên của iframe. (Chỉ để bạn biết.)

Tôi hi vọng cái này giúp được


AFAIK FormData không hoạt động với IE. Có lẽ đó là một ý tưởng tốt hơn để thực hiện mã hóa base64 của tệp hình ảnh và gửi nó trong JSON? Làm cách nào tôi có thể liên kết với kiểu nhập = "tệp" với AngularJS để có đường dẫn tệp đã chọn?
Gal Ben-Haim

3

Tôi biết đây là một mục muộn nhưng tôi đã tạo một chỉ thị tải lên đơn giản. Mà bạn có thể làm việc trong thời gian không!

<input type="file" multiple ng-simple-upload web-api-url="/api/post"
       callback-fn="myCallback" />

ng-đơn giản - tải lên nhiều hơn trên Github với một ví dụ sử dụng API Web.


2
thành thật mà nói, đề xuất sao chép-dán mã trong readme dự án của bạn có thể là một dấu đen lớn. hãy thử tích hợp dự án của bạn với các trình quản lý gói phổ biến như npm hoặc bower.
Stefano Torresi 7/03/2016

2

Bạn có thể tải lên qua $resourcebằng cách gán dữ liệu cho thuộc tính params của tài nguyên actionsnhư vậy:

$scope.uploadFile = function(files) {
    var fdata = new FormData();
    fdata.append("file", files[0]);

    $resource('api/post/:id', { id: "@id" }, {
        postWithFile: {
            method: "POST",
            data: fdata,
            transformRequest: angular.identity,
            headers: { 'Content-Type': undefined }
        }
    }).postWithFile(fdata).$promise.then(function(response){
         //successful 
    },function(error){
        //error
    });
};

1

Tôi chỉ viết một chỉ thị đơn giản (từ một tài liệu hiện có) cho một người tải lên đơn giản trong AngularJs.

(Plugin tải lên jQuery chính xác là https://github.com/blueimp/jQuery-File-Upload )

Trình tải lên đơn giản sử dụng AngularJs (có triển khai CORS)

(Mặc dù phía máy chủ dành cho PHP, bạn cũng có thể đơn giản thay đổi nút đó)


1
Đừng quên đề cập rằng bạn không dành thời gian để đóng \ phản hồi các vấn đề trên repo của bạn
Oleg Belousov

@OlegTikhonov: Yea, nghiêm túc mắc kẹt với một số dự án khác. Khó có thể tập trung.
sk8terboi87
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.