AngularJS: jak zaimplementować proste przesyłanie plików z formularzem wieloczęściowym?

144

Chcę zrobić prosty post w postaci wieloczęściowej z AngularJS na serwer node.js, formularz powinien zawierać obiekt JSON w jednej części i obraz w drugiej części (obecnie publikuję tylko obiekt JSON z zasobem $)

Pomyślałem, że powinienem zacząć od input type = "file", ale potem odkryłem, że AngularJS nie może się z tym powiązać.

wszystkie przykłady, które mogę znaleźć, dotyczą pakowania wtyczek jQuery do przeciągania i upuszczania. Chcę proste przesłanie jednego pliku.

Jestem nowy w AngularJS i nie czuję się komfortowo, pisząc własne dyrektywy.

Gal Ben-Haim
źródło
1
myślę, że to może pomóc: noypi-linux.blogspot.com/2013/04/…
Noypi Gilas
1
Zobacz tę odpowiedź: stackoverflow.com/questions/18571001/… Istnieje wiele opcji dla już działających systemów.
Anoyz
1
Zobacz tutaj
bobobobo

Odpowiedzi:

188

Prawdziwie działające rozwiązanie bez innych zależności niż angularjs (testowane z wersją 1.0.6)

html

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

Angularjs (1.0.6) nie obsługuje modelu ng w tagach "pliku wejściowego", więc musisz to zrobić w "natywny sposób", który przekazuje wszystkie (ostatecznie) wybrane pliki od użytkownika.

kontroler

$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!... );

};

Fajną częścią jest niezdefiniowany typ zawartości i transformRequest: angular.identity, które dają w $ http możliwość wyboru właściwego „typu treści” i zarządzania granicami wymaganymi podczas obsługi danych wieloczęściowych.

Fabio Bonfante
źródło
2
@Fabio Czy u plz możesz mi wyjaśnić, co robi ta transformRequest? co to jest angular.identity? Przez cały dzień pracowałem głową tylko po to, aby przesłać plik wieloczęściowy.
agpt
1
@RandomUser w aplikacji java rest, coś takiego mkyong.com/webservices/jax-rs/file-upload-example-in-jersey
Fabio Bonfante
2
wow, po prostu genialne, wielkie dzięki. Tutaj muszę przesłać wiele zdjęć i kilka innych danych, więc manipuluję fd jakofd.append("file", files[0]); fd.append("file",files[1]); fd.append("name", "MyName")
Moshii
1
console.log (fd) pokazuje, że formularz jest pusty? czy to w ten sposób?
J Bourne,
4
Zauważ, że to nie zadziała, jeśli masz wyłączone debugInfo (zgodnie z zaleceniami)
Bruno Peres
42

Możesz użyć prostej / lekkiej dyrektywy ng-file-upload . Obsługuje przeciąganie i upuszczanie, postęp plików i przesyłanie plików dla przeglądarek innych niż HTML5 z podkładką Flash 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);
  }); 

}];
danial
źródło
Czy to nie jest wykonywanie pojedynczego żądania POST dla każdego pliku naraz?
Anoyz,
Tak. W narzędziu do śledzenia problemów na githubie są dyskusje na temat tego, dlaczego lepiej jest przesyłać pliki jeden po drugim. Flash API nie obsługuje jednoczesnego przesyłania plików, a AFAIK Amazon S3 również go nie obsługuje.
danial
Więc mówisz, że ogólnie bardziej poprawnym podejściem jest wysyłanie jednego żądania POST pliku na raz? Widzę w tym kilka zalet, ale także więcej kłopotów przy zapewnianiu spokojnego wsparcia po stronie serwera.
Anoyz
2
Sposób, w jaki to zaimplementowałem, polega na przesłaniu każdego pliku jako zapisu zasobów, a serwer zapisze go w lokalnym systemie plików (lub bazie danych) i zwróci unikalny identyfikator (tj. Losową nazwę folderu / pliku lub identyfikator bazy danych) dla tego pliku. Następnie, po zakończeniu wszystkich operacji przesyłania, klient wysyła kolejne żądanie PUT / POST, podając dodatkowe dane i identyfikatory plików, które zostały przesłane dla tego żądania. Następnie serwer zapisze rekord wraz z powiązanymi plikami. To jak Gmail, kiedy przesyłasz pliki, a następnie wysyłasz e-mail.
danial
1
To nie jest „proste / lekkie”. W sekcji próbek nie ma nawet przykładu przesłania tylko jednego pliku.
Chris,
9

Bardziej efektywne jest bezpośrednie wysłanie pliku.

Base64 kodowania z Content-Type: multipart/form-datadodaje dodatkowy 33% narzutu. Jeśli serwer to obsługuje, bardziej efektywne jest wysyłanie plików bezpośrednio:

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

Wysyłając POST z obiektem File , ważne jest, aby ustawić 'Content-Type': undefined. Metoda wysyłania XHR wykryje wtedy obiekt File i automatycznie ustawi typ zawartości.

Aby wysłać wiele plików, zobacz Wykonywanie wielu żądań bezpośrednio z listy plików$http.post


Pomyślałem, że powinienem zacząć od input type = "file", ale potem odkryłem, że AngularJS nie może się z tym powiązać.

<input type=file>Element nie domyślnie pracy z dyrektywą ng-modelu . Potrzebuje niestandardowej dyrektywy :

Robocze demo dyrektywy „select-ng-files”, która współpracuje z 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 z typem treści multipart/form-data

Jeśli trzeba wysłać 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);
};

Podczas wysyłania POST z API FormData ważne jest, aby ustawić 'Content-Type': undefined. Metoda wysyłania XHR wykryje następnie FormDataobiekt i automatycznie ustawi nagłówek typu zawartości na multipart / form-data z odpowiednią granicą .

georgeawg
źródło
Wydaje się, że filesInputdyrektywa nie działa z Angular 1.6.7, widzę pliki w, ng-repeatale to samo $scope.variablejest puste w kontrolerze? Również jeden z twoich przykładów używafile-model i jedenfiles-input
Dan
@Dan Przetestowałem to i działa. Jeśli masz problem z kodem, powinieneś zadać nowe pytanie z minimalnym, kompletnym, weryfikowalnym przykładem . Zmieniono nazwę dyrektywy na select-ng-files. Testowane z AngularJS 1.7.2.
georgeawg
5

Właśnie miałem ten problem. Więc jest kilka podejść. Po pierwsze, nowe przeglądarki obsługują rozszerzenie

var formData = new FormData();

Skorzystaj z tego linku do bloga z informacjami o tym, jak pomoc techniczna jest ograniczona do nowoczesnych przeglądarek, ale poza tym całkowicie rozwiązuje ten problem.

W przeciwnym razie możesz wysłać formularz do elementu iframe, używając atrybutu target. Podczas wysyłania formularza pamiętaj, aby ustawić cel na element iframe z właściwością wyświetlania ustawioną na none. Cel to nazwa elementu iframe. (Tak, żebyś wiedział.)

mam nadzieję, że to pomoże

Edgar Martinez
źródło
AFAIK FormData nie działa z IE. może lepszym pomysłem będzie kodowanie base64 pliku obrazu i wysłanie go w formacie JSON? jak mogę powiązać się z typem wejściowym = "plik" za pomocą AngularJS, aby uzyskać wybraną ścieżkę do pliku?
Gal Ben-Haim,
3

Wiem, że to późny wpis, ale stworzyłem prostą dyrektywę przesyłania. Które możesz rozpocząć w mgnieniu oka!

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

ng-simple-upload więcej na Github z przykładem przy użyciu interfejsu API sieci Web.

shammelburg
źródło
2
szczerze mówiąc, sugerowanie kopiowania i wklejania kodu w pliku readme projektu może być dużym czarnym znakiem. spróbuj zintegrować swój projekt z popularnymi menedżerami pakietów, takimi jak npm lub bower.
Stefano Torresi
2

Możesz przesłać za pośrednictwem $resource, przypisując dane do atrybutu params zasobu w następujący actionssposób:

$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
    });
};
Emeka Mbah
źródło
1

Właśnie napisałem prostą dyrektywę (oczywiście z istniejącej) dla prostego programu do przesyłania plików w AngularJs.

(Dokładna wtyczka do przesyłania jQuery to https://github.com/blueimp/jQuery-File-Upload )

Prosty program do przesyłania korzystający z AngularJs (z implementacją CORS)

(Chociaż strona serwera jest przeznaczona dla PHP, możesz również po prostu zmienić jej węzeł)

sk8terboi87 ツ
źródło
1
Nie zapomnij wspomnieć, że nie dajesz czasu na zamknięcie \ reagowanie na problemy w swoim repozytorium
Oleg Belousov
@OlegTikhonov: Tak, poważnie zająłem się kilkoma innymi projektami. Ledwo mogę się skupić.
sk8terboi87,