AngularJS: como implementar um upload de arquivo simples com formulário de várias partes?

144

Quero fazer uma postagem simples de formulário com várias partes do AngularJS para um servidor node.js.O formulário deve conter um objeto JSON em uma parte e uma imagem na outra parte (atualmente, estou postando apenas o objeto JSON com $ resource)

Achei que deveria começar com input type = "file", mas depois descobri que o AngularJS não pode vincular a isso ..

todos os exemplos que posso encontrar são para agrupar plugins jQuery para arrastar e soltar. Quero um upload simples de um arquivo.

Eu sou novo no AngularJS e não me sinto à vontade em escrever minhas próprias diretivas.

Gal Ben-Haim
fonte
1
Eu acho que isso pode ajudar: noypi-linux.blogspot.com/2013/04/…
Noypi Gilas
1
Veja esta resposta: stackoverflow.com/questions/18571001/… Diversas opções para sistemas já em funcionamento.
Anoyz
1
Veja aqui
bobobobo

Respostas:

188

Uma solução real de trabalho sem outras dependências além do angularjs (testado com a v.1.0.6)

html

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

O Angularjs (1.0.6) não suporta ng-model em tags "input-file", então você deve fazê-lo de uma "maneira nativa" que passe todos os arquivos selecionados (eventualmente) do usuário.

controlador

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

};

A parte interessante é o tipo de conteúdo indefinido e o transformRequest: angular.identity que permite ao $ http a capacidade de escolher o "tipo de conteúdo" certo e gerenciar o limite necessário ao manipular dados de várias partes.

Fabio Bonfante
fonte
2
@ Fabio Você pode me explicar o que esse transformRequest faz? qual é a identidade angular? Eu estava batendo a cabeça durante o dia apenas para realizar o upload de arquivos com várias partes.
agpt 21/02
1
@RandomUser em uma aplicação java resto, algo assim mkyong.com/webservices/jax-rs/file-upload-example-in-jersey
Fabio Bonfante
2
uau, simplesmente brilhante, muito obrigado. Aqui eu tenho que fazer upload de várias imagens e alguns outros dados, bem assim i manipular fd comofd.append("file", files[0]); fd.append("file",files[1]); fd.append("name", "MyName")
Moshii
1
console.log (fd) mostra que o formulário está vazio? é assim?
J Bourne
4
Note que isto não vai funcionar se você tem DebugInfo desativado (como recomendado)
Bruno Peres
42

Você pode usar a diretiva ng / upload de arquivos ng simples / leve . Ele suporta arrastar e soltar, progresso de arquivos e upload de arquivos para navegadores não HTML5 com calço FileAPI flash

<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
fonte
Isso não está executando uma única solicitação POST para cada arquivo por vez?
Anoyz
Sim. Há discussões no rastreador de problemas do github sobre por que é melhor fazer upload dos arquivos um por um. A API do Flash não suporta o envio de arquivos juntos e o AFAIK Amazon S3 também não.
Danial
Então, você está dizendo que a abordagem geral mais correta é enviar uma solicitação POST de arquivo por vez? Vejo várias vantagens nisso, mas também mais problemas ao oferecer suporte tranqüilo no servidor.
Anoyz
2
A maneira como eu o implemento é fazer o upload de cada arquivo como um recurso economizado e o servidor o salvará no sistema de arquivos local (ou banco de dados) e retornará um ID exclusivo (por exemplo, nome aleatório da pasta / arquivo ou ID do banco de dados) para esse arquivo. Depois que todos os uploads forem concluídos, o cliente envia outra solicitação PUT / POST com dados e IDs extras dos arquivos enviados para essa solicitação. Em seguida, o servidor salvaria o registro com os arquivos associados. É como o gmail quando você faz upload de arquivos e depois envia o email.
Danial
1
Isso não é "simples / leve". A seção de amostras nem tem um exemplo de upload de apenas um arquivo.
Chris
9

É mais eficiente enviar um arquivo diretamente.

A codificação base64 de Content-Type: multipart/form-dataadiciona uma sobrecarga extra de 33%. Se o servidor suportar, é mais eficiente enviar os arquivos diretamente:

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

Ao enviar um POST com um objeto File , é importante definir 'Content-Type': undefined. O método de envio XHR detectará o objeto File e definirá automaticamente o tipo de conteúdo.

Para enviar vários arquivos, consulte Fazendo várias solicitações diretamente de uma lista de arquivos$http.post


Achei que deveria começar com input type = "file", mas depois descobri que o AngularJS não pode vincular a isso ..

O <input type=file>elemento não funciona por padrão com a diretiva ng-model . Ele precisa de uma diretiva personalizada :

Demonstração de trabalho da diretiva "select-ng-files" que funciona com 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 com tipo de conteúdo multipart/form-data

Se for necessário enviar 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);
};

Ao enviar um POST com a API FormData , é importante definir 'Content-Type': undefined. O método de envio XHR detectará o FormDataobjeto e definirá automaticamente o cabeçalho do tipo de conteúdo como multipart / form-data com o limite apropriado .

georgeawg
fonte
A filesInputdiretiva não parece estar funcionando com o Angular 1.6.7, posso ver os arquivos no ng-repeatmas o mesmo $scope.variableestá em branco no controlador? Também um de seus exemplos usa file-modele umfiles-input
Dan
@ Dan eu testei e funciona. Se você estiver tendo problemas com seu código, faça uma nova pergunta com um exemplo Mínimo, Completo e Verificável . O nome da diretiva foi alterado para select-ng-files. Testado com AngularJS 1.7.2.
Georgeawg 29/08/1918
5

Eu apenas tive esse problema. Portanto, existem algumas abordagens. A primeira é que novos navegadores suportam o

var formData = new FormData();

Siga este link para um blog com informações sobre como o suporte é limitado aos navegadores modernos, mas, caso contrário, ele resolve totalmente esse problema.

Caso contrário, você pode postar o formulário em um iframe usando o atributo target. Ao publicar o formulário, defina o destino como um iframe com a propriedade de exibição definida como none. O destino é o nome do iframe. (Só para você saber.)

Eu espero que isso ajude

Edgar Martinez
fonte
O AFAIK FormData não funciona com o IE. talvez seja uma idéia melhor fazer a codificação base64 do arquivo de imagem e enviá-lo em JSON? como posso vincular a um input type = "file" com AngularJS para obter o caminho do arquivo escolhido?
Gal Ben-Haim
3

Sei que é uma entrada tardia, mas criei uma diretiva de upload simples. Que você pode começar a trabalhar rapidamente!

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

ng-simple-upload mais no Github com um exemplo usando a API da Web.

Shammelburg
fonte
2
para ser sincero, sugerir copiar e colar o código no leia-me do projeto pode ser uma grande marca negra. tente integrar seu projeto a gerenciadores de pacotes comuns, como npm ou bower.
Stefano Torresi 07/03
2

Você pode fazer o upload via $resourceatribuindo dados ao atributo params do recurso da seguinte actionsforma:

$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
fonte
1

Acabei de escrever uma diretiva simples (a partir de uma existente) para um upload simples no AngularJs.

(O plugin exato do uploader do jQuery é https://github.com/blueimp/jQuery-File-Upload )

Um Uploader Simples usando AngularJs (com Implementação CORS)

(Embora o lado do servidor seja para PHP, você pode simplesmente mudar o nó também)

sk8terboi87 ツ
fonte
1
Não se esqueça de mencionar que você não dá a qualquer momento para fechar \ responder a questões no seu repo
Oleg Belousov
@OlegTikhonov: Sim, seriamente preso com alguns outros projetos. Dificilmente capaz de se concentrar.
Sk8terboi87 #