Angular y Spring. Múltiple subida de archivos (Frontend) con Angular

En este tutorial vamos a realizar el frontend en Angular para subir múltiples/varios archivos, se utiliza Angular Material para el aspecto de la aplicación. Si quieres ver el backend de esta aplicación da clic aqui.

El resultado que vamos a obtener con este proyecto en la parte Front en Angular es este:

Creación y configuración del proyecto en Angular

Abrimos la consola de comandos y escribimos lo siguiente

ng new UploadFilesFront
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSS

Nos ubicamos en la carpeta de nuestro proyecto y creamos un componente

ng g c components/upload-files

Luego generamos un servicio:

ng g s services/upload-files

Añadimos Angular Material con el siguiente comando

ng add @angular/material

Deberías tener una estructura como la siguiente

Estructura del proyecto base
  • Component upload-files.component –> Componente donde vamos a crear el html que vamos a mostrar.
  • Services upload-files.component –> Servicio en el cual vamos a hacer la llamada a los endpoints o Apis Rest para subir archivos, obtenerlos y borrarlos

Importando los módulos en app.module

Nos ubicamos en el archivo app.module.ts para hacer la importación de los módulos necesarios del proyecto.

import { HttpClientModule } from '@angular/common/http';
import {MatProgressBarModule} from '@angular/material/progress-bar';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatIconModule} from '@angular/material/icon';
import {MatGridListModule} from '@angular/material/grid-list';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { UploadFilesComponent } from './components/upload-files/upload-files.component';
import { HttpClientModule } from '@angular/common/http';
import {MatProgressBarModule} from '@angular/material/progress-bar';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatIconModule} from '@angular/material/icon';

@NgModule({
  declarations: [
    AppComponent,
    UploadFilesComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    BrowserAnimationsModule,
    MatProgressBarModule,
    MatButtonModule,
    MatCardModule,
    MatIconModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
  • HttpClientModule –> Este módulo se utiliza para poder realizar las peticiones a las Apis Rest.
  • MatProgressBarModule –> Módulo para utilizar el progress bar de Angular Material
  • MatButtonModule –> Módulo para utilizar botones de Angular Material
  • MatCardModule –> Módulo para utilizar el componente card de Angular Material
  • MatIconModule –> Módulo para utilizar los iconos de Angular Material

Añadiendo la URL Base de los endpoints

Nos dirigimos al archivo enviroment.ts que se encuentra en la carpeta enviroments/enviroment.ts donde vamos a crear una variable llamada baseUrl donde vamos a almacenar la url base de nuestros endpoints.

export const environment = {
  production: false,
  baseUrl: 'http://localhost:8080'
};

Creación del servicio para subir, obtener y eliminar varios archivos

Nos ubicamos en el archivo upload-files.service.ts

import { Injectable } from '@angular/core';
import { environment } from '../../environments/environment';
import { HttpClient, HttpRequest, HttpEvent,HttpParams} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class UploadFilesService {

  //Url obtenida de la variable de enviroments
  baseUrl = environment.baseUrl;

  //Inyeccion de HttpClient
  constructor(private http: HttpClient) { }

  //Metodo que envia los archivos al endpoint /upload 
  upload(file: File): Observable<HttpEvent<any>>{
    const formData: FormData = new FormData();
    formData.append('files', file);
   
    const req = new HttpRequest('POST', `${this.baseUrl}/upload`, formData, {
      reportProgress: true,
      responseType: 'json'
    });
    return this.http.request(req);
  }

  //Metodo para Obtener los archivos
  getFiles(){
    return this.http.get(`${this.baseUrl}/files`);
  }

  //Metodo para borrar los archivos
  deleteFile(filename: string){
    return this.http.get(`${this.baseUrl}/delete/${filename}`);
  }

}
  • FormData –> Es una estructura de datos que almacena parejas clave – valor. Se usa para construir el objeto que vamos a mandar a la Api.
  • ReportProgress –> Se pone en true para mostrar el progreso de la petición HTTP
  • Utilizamos peticiones POST y GET

Creación del component.ts

Nos dirigimos al componente previamente creado upload-files.component.ts

Primero importamos el servicio previamente creado, Observable, HttpEventType y HttpResponse

import { Component, OnInit } from '@angular/core';
import { UploadFilesService } from '../../services/upload-files.service';
import { Observable } from 'rxjs';
import { HttpEventType, HttpResponse } from '@angular/common/http';

Lo siguiente es crear estas variables:

  //Lista de archivos seleccionados
  selectedFiles: FileList;
  //Es el array que contiene los items para mostrar el progreso de subida de cada archivo
  progressInfo = []
  //Mensaje que almacena la respuesta de las Apis
  message = '';
  //Nombre del archivo para usarlo posteriormente en la vista html
  fileName = "";
  fileInfos: Observable<any>;

Inyectamos el servicio al constructor:

 constructor(private uploadFilesService: UploadFilesService) { }

Creamos un método que permita obtener los archivos seleccionados:

  selectFiles(event) {
    this.progressInfo = [];
    //Validación para obtener el nombre del archivo si es uno solo
    //En caso de que sea >1 asigna a fileName length
    event.target.files.length == 1 ? this.fileName = event.target.files[0].name : this.fileName = event.target.files.length + " archivos";
    this.selectedFiles = event.target.files;
  }

Creamos un método que recorra uno por uno los archivos seleccionados para ir llamando el método de subida (upload).

uploadFiles() {
    this.message = '';
    for (let i = 0; i < this.selectedFiles.length; i++) {
      this.upload(i, this.selectedFiles[i]);
    }
  }

Creación del método upload()

 upload(index, file) {
    this.progressInfo[index] = { value: 0, fileName: file.name };

    this.uploadFilesService.upload(file).subscribe(
      event => {
        if (event.type === HttpEventType.UploadProgress) {
          this.progressInfo[index].value = Math.round(100 * event.loaded / event.total);
        } else if (event instanceof HttpResponse) {
          this.fileInfos = this.uploadFilesService.getFiles();
        }
      },
      err => {
        this.progressInfo[index].value = 0;
        this.message = 'No se puede subir el archivo ' + file.name;
      });
  }
  • index –> Índice del archivo actual
  • progressInfo –> Valor del progreso actual de la subida del archivo, el cual vamos a pasar al matprogressbar

El siguiente método que creamos es para borrar un archivo por nombre:

  deleteFile(filename: string) {
    this.uploadFilesService.deleteFile(filename).subscribe(res => {
      this.message = res['message'];
      this.fileInfos = this.uploadFilesService.getFiles();
    });
  }

En el ngOnInit hacemos la llamada al método de obtener los archivos:

  ngOnInit(): void {
    this.fileInfos = this.uploadFilesService.getFiles();
  }

La clase completa se ve de esta manera:

import { Component, OnInit } from '@angular/core';
import { UploadFilesService } from '../../services/upload-files.service';
import { Observable } from 'rxjs';
import { HttpEventType, HttpResponse } from '@angular/common/http';

@Component({
  selector: 'app-upload-files',
  templateUrl: './upload-files.component.html',
  styleUrls: ['./upload-files.component.css']
})
export class UploadFilesComponent implements OnInit {

  selectedFiles: FileList;
  //Es el array que contiene los items para mostrar el progreso de subida de cada archivo
  progressInfo = []
  message = '';
  fileName = "";
  fileInfos: Observable<any>;

  constructor(private uploadFilesService: UploadFilesService) { }

  ngOnInit(): void {
    this.fileInfos = this.uploadFilesService.getFiles();
  }

  selectFiles(event) {
    this.progressInfo = [];
    event.target.files.length == 1 ? this.fileName = event.target.files[0].name : this.fileName = event.target.files.length + " archivos";
    this.selectedFiles = event.target.files;
  }

  uploadFiles() {
    this.message = '';
    for (let i = 0; i < this.selectedFiles.length; i++) {
      this.upload(i, this.selectedFiles[i]);
    }
  }

  upload(index, file) {
    this.progressInfo[index] = { value: 0, fileName: file.name };

    this.uploadFilesService.upload(file).subscribe(
      event => {
        if (event.type === HttpEventType.UploadProgress) {
          this.progressInfo[index].value = Math.round(100 * event.loaded / event.total);
        } else if (event instanceof HttpResponse) {
          this.fileInfos = this.uploadFilesService.getFiles();
        }
      },
      err => {
        this.progressInfo[index].value = 0;
        this.message = 'No se puede subir el archivo ' + file.name;
      });
  }

  deleteFile(filename: string) {
    this.uploadFilesService.deleteFile(filename).subscribe(res => {
      this.message = res['message'];
      this.fileInfos = this.uploadFilesService.getFiles();
    });
  }

}

Creación del component.html

<div id="wrap">

    <mat-card id="box1">
        <div mode="buffer" *ngFor="let progress of progressInfo">
            <span>{{progress.fileName}}</span>
            <span>{{progress.value}}%</span>
            <div id="progressbar">
                <mat-progress-bar mode="buffer" value={{progress.value}}>
                </mat-progress-bar>
            </div>
        </div>
    </mat-card>

    <div id="box2">
        <button id="selectButton" mat-raised-button (click)="fileInput.click()">Select File to Upload</button>

        <input #fileInput type="file" hidden multiple (change)="selectFiles($event)" />
        <span *ngIf="fileName">{{fileName}}</span>

        <button id="uploadButton" mat-raised-button *ngIf="selectedFiles" [disabled]="!selectedFiles" (click)="uploadFiles()">
            Subir Archivos
        </button>
    </div>

    <div id="box3">
        <span>{{message}}</span>
    </div>


    <div id="box4">
        <mat-card>
            <mat-card-header>
                <mat-card-title>Lista de Archivos</mat-card-title>
            </mat-card-header>
            <mat-card-content>
                <ul>
                    <li *ngFor="let file of fileInfos | async">
                        <a href="{{file.url}}">{{file.name}}</a>
                        <mat-icon (click)="deleteFile(file.name)">delete</mat-icon>
                    </li>
                </ul>
            </mat-card-content>
        </mat-card>
    </div>
</div>

Estilos del Component.html

#wrap{
   display: block;
}

#box1, #box2, #box3, #box4{
    display: block;
    width: 50%;
    margin-left: auto;
    margin-right: auto;
    margin-top: 10px;
    margin-bottom: 10px;
    text-align: center;
}

#box1{
    text-align: center;
    border: 1px solid gray;
    height: 250px;
    overflow: auto;
}

#progressbar{
    text-align: center;
    height: 20px;
    padding: 10px;
}

#box1 span{
    margin-left: 10px;
}

#box2 span{
    margin-left: 10px;
    margin-right: 10px;
}

#box2 #selectButton{
    background-color: rgb(1, 155, 1);
}

#box2 #uploadButton{
    background-color: rgb(26, 26, 160);
}

#box3{
    color: red;
}

#box4 ul{
 list-style: none;
}

#box4 ul li{
  display: flex;
  justify-content: space-between;
  align-items: center;
}

#box4 ul li a{
 text-decoration: none;
 color: white;
 margin-right: 20px;
}

Añadiendo el componente a app.component.html

 <div id="titulo">
   <h1>Subir múltiples archivos Angular y Java</h1>
</div>

<div>
  <app-upload-files></app-upload-files>
  <router-outlet></router-outlet>
</div>
#titulo{
    display: block;
    width: 100%;
    margin-left: auto;
    margin-right: auto;
    height: 50px;
    line-height: 50px;
    padding: 20px;
    text-align: center;
}

Corriendo la aplicación

Para correr la aplicación ejecutamos el comando ng serve, abrimos el navegador en el puerto que corresponda

resultado
Resultado esperado
resultadoBack
Imágenes subidas en el store del backend

El repositorio de este proyecto por si tienes dudas:

Este tutorial lo encuentras en video en mi canal de YouTube:

Proyecto Backend Java Spring Boot

Da clic en el siguiente link para encontrar el tutorial de como hacer el backend de esta aplicación

2 Replies to “Angular y Spring. Múltiple subida de archivos (Frontend) con Angular”

  1. Buen día he seguido tu blog y son muy buenos tutoriales.
    Una pregunta con respecto el front end, a la hora de desplegar los archivos que se encuentran en el back end. Se despliegan de manera correcta. pero a la hora de refrescar la página (localhost) se pierden en pantalla. ¿Como le puedo hacer para que se queden de manera estática la lista de los archivos. Para que a la hora de recargar o ingresar a la aplicación me muestre la lista de archivos que ya están cargados en e backend.
    De antemano gracias por el apoyo.

Leave a Reply

Your email address will not be published.