Angular 2+ Upload de arquivo com barra de progresso
Veja como construir um componente de barra de progresso, como fazer upload de arquivos e como integrar o upload com a barra de progresso - Angular 2+
O objetivo deste artigo é mostrar como construir um componente de barra de progresso e como efetuar um upload mostrando a porcentagem em tempo real.
Construção do componente barra de progresso
Sem muita enrolação, vamos criar um módulo para o componente barra de progresso:
Para quem está começando, o exports
faz com que o componente BarraProgressoComponent
possa ser utilizado por um outro módulo que importe este módulo.
O componente terá apenas uma entrada de dados, que será o valor da barra de progresso em porcentagem:
O estilo visual ficará bem simples e funcional, então um pouco de CSS já resolverá o problema:
Para quem ainda não utilizou, o :host
refere-se ao <app-barra-progresso>
. Da mesma forma que aplicamos um estilo em cima de um <button>
ou um <div>
, podemos também aplicar (utilizando o :host
) em cima do seletor do nosso componente, que neste caso é o app-barra-progresso
.
Por fim o template do componente:
Testando o componente barra de progresso
Agora que o componente está pronto, vamos importá-lo no AppModule
e adicionar algumas referencias no template do AppComponent
para ver como ele é renderizado na tela:
É possível ver que a renderização da barra de progresso está respeitando a porcentagem que passamos no [porcentagemAtual]
.
Vamos avançar um pouco mais e ver como integrar essa barra de progresso ao upload de um arquivo.
Upload de arquivos com barra de progresso
Para quem já trabalhou com upload de arquivos utilizando AJAX, já deve conhecer bem o FormData
.
A interfaceFormData
fornece uma maneira fácil de construir um conjunto de pares chave/valor representando campos de um elemento form
e seus valores, os quais podem ser facilmente enviados utilizado o métodosend()
do XMLHttpRequest. Essa interface utiliza o mesmo formato que umform
utilizaria se o tipo de codificação estivesse configurado como "multipart/form-data".
fonte: https://developer.mozilla.org/pt-BR/docs/Web/API/FormData
O FormData
utiliza a estrutura de dados multipart/form-data
, que inclusive comentei em um outro post: Upload de arquivos e imagens utilizando o Multer - express - Node.js
Então a ideia é bem simples, vamos criar um formulário com um <input type="file">
e no evento onChange
vamos pegar os detalhes do arquivo que o usuário selecionou no método preview
:
Veja na animação abaixo que quando selecionamos o arquivo, o evento (change)
é disparado e o método preview
recebe os detalhes do evento. Nestes detalhes conseguimos obter algumas informações do arquivo selecionado:
Preview do upload
Antes de entrarmos no upload, vamos mostrar na tela um preview do que o usuário selecionou para upload. Para efetuar essa tarefa, vamos precisar da ajuda do FileReader
, que nos permite ler o bytes do arquivo selecionado e guardá-los em uma variável.
O objetoFileReader
permite aplicações web ler assincronamente o conteúdo dos arquivos (ou buffers de dados puros) do computador do usuário, utilizando o objetoFile
ouBlob
para especificar o arquivo ou os dados a serem lidos.
fonte: https://developer.mozilla.org/pt-BR/docs/Web/API/FileReader
Vamos fazer as seguintes alterações:
E no template:
<label for="meuArquivo">Arquivo</label>
<input type="file" id="meuArquivo" (change)="preview($event)" />
<div class="preview">
<img *ngIf="arquivoPreview" [src]="arquivoPreview" alt="preview" />
</div>
Observe que o this.arquivoPreview
é passado no [src]
do <img>
e o resultado é:
Efetuando o upload do arquivo
Antes de mais nada, certifique-se de ter importado o módulo HttpClientModule
do @angular/common/http
para não pegar o erro:
ERROR NullInjectorError: R3InjectorError(AppModule)[HttpClient -> HttpClient -> HttpClient]: NullInjectorError: No provider for HttpClient!
Para efetuar o teste do upload, utilizei uma API construída no artigo: Upload de arquivos e imagens utilizando o Multer - express - Node.js
Então nosso endereço de API que receberá o upload será: localhost:3000/arquivos
e minha aplicação angular está rodando em localhost:4200
. Então para não termos problemas com CORS:
- Chrome Windows: execute-o utilizando a seguinte opção:
chrome.exe" --disable-web-security --disable-gpu --user-data-dir=~/chromeTemp
. - Chrome macOS:
open -n -a /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security
- Safari: vá até a aba Developer e selecione a opção
Disable Cross-Origin Restrictions
As alterações no template ficaram bem simples, basicamente foi adicionado um <button>
e um evento de (click)
:
<label for="meuArquivo">Arquivo</label>
<input type="file" id="meuArquivo" (change)="preview($event)" />
<div class="preview">
<img *ngIf="arquivoPreview" [src]="arquivoPreview" alt="preview" />
</div>
<button (click)="efetuarUpload()"
type="button">Upload</button>
E no componente construímos o método efetuarUpload()
ativado pelo clique do botão Upload:
Veja que ao selecionar o arquivo e clicar no botão Upload
, é efetuada uma requisição que retorna o httpStatusCode 201
indicando que o registro foi criado com sucesso:
Mostrando a porcentagem do upload com a barra de progresso
Para finalizar, vamos integrar a barra de progresso com a porcentagem do upload. Para isto precisamos "dizer" ao Angular que observe os eventos emitidos pela requisição HTTP e também nos reporte sobre o progresso do upload. Para fazer isto basta informar um terceiro argumento ({ reportProgress: true, observe: 'events' }
) na chamada do http.post
conforme o código abaixo mostra:
Antes dessa alteração o subscribe
emitia um valor quando a requisição era finalizada. Agora o subscribe
emitirá vários valores e eventos, então vamos pegar o evento UploadProgress
para calcular a porcentagem do upload e depois pegar o Response
indicando que a requisição foi finalizada.
Durante o UploadProgress
calculamos a porcentagem do upload e passamos o valor para uma variável chamada porcentagemUpload
que é passada como parâmetro na barra de progresso, veja no template:
Não se esqueça de importar o BarraProgressoModule
no seu AppModule
!
Como a API está rodando no meu computador, utilizei um recurso bem interessante do Chrome para simular uma redução na velocidade das requisições. Na aba Network
tem um dropdown com a opção padrão Online
que pode ser alterada para por exemplo, slow 3G
, para simular uma conexão mais lenta. Muito útil durante o desenvolvimento :)
Desta forma conseguimos ver bem a barra de progresso trabalhando, veja no GIF abaixo o resultado final:
Considerações
Neste artigo mostramos como construir um componente de barra de progresso, como efetuar o upload de arquivos utilizando Angular e como integrar a porcentagem do upload com a barra de progresso.
Vou deixar abaixo a implementação do projeto, lembrando que como o projeto está rodando no stackblitz, o localhost:3000
(API que recebe o upload) não irá funcionar, então apontei para um link do mocky.io só para exemplificar mas deixei comentado no código:
Links interessantes: