Spring Boot + Angular: File Upload & Download Example
Hello everyone, today we will learn how to upload and download file with Spring Boot and Angular. You can download the source code from our GitHub repository.
Backend Project Directory:
Frontend Project Directory:
We will build two projects:
1. Backend: springboot-fileupload-filedownload
2. Frontend: angular-file-upload
Project 1: springboot-file-upload-download
pom.xml
<?xml version="1.0" encoding="UTF-8"?><project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.0</version> <!-- lookup parent from repository --> </parent> <groupId>com.knowledgefactory</groupId> <artifactId>springboot-file-upload-download</artifactId> <packaging>jar</packaging> <version>0.0.1-SNAPSHOT</version> <name>springboot-file-upload-download</name>
<properties> <java.version>17</java.version> </properties>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- spring mvc, rest --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <description>springboot-file-upload-download</description></project>
UploadDownloadService.java
@Servicepublic class UploadDownloadService { private static final String path = "/home/user/Desktop/files";
public List<String> uploadFile(MultipartFile file) throws Exception {
// Save file on system if (!file.getOriginalFilename().isEmpty()) {
BufferedOutputStream outputStream = new BufferedOutputStream( new FileOutputStream(new File(path, file.getOriginalFilename())));
outputStream.write(file.getBytes()); outputStream.flush(); outputStream.close();
} else { throw new Exception(); }
List<String> list = new ArrayList<String>(); File files = new File(path); String[] fileList = files.list(); for (String name : fileList) { list.add(name); }
return list;
}
public List<String> getListofFiles() throws Exception {
List<String> list = new ArrayList<String>(); File files = new File(path); String[] fileList = ((File) files).list(); for (String name : fileList) { list.add(name); }
return list;
}}
FileController.java
@CrossOrigin(origins = "*", maxAge = 3600)@RestControllerpublic class FileController {
private static final String path = "/home/user/Desktop/files/";
@Autowired UploadDownloadService service;
@PostMapping("/upload") public ResponseEntity<List<String>> fileUpload (@RequestParam("file") MultipartFile file) throws Exception {
return new ResponseEntity<>(service.uploadFile(file), HttpStatus.OK);
}
@GetMapping(path = "/download/{name}") public ResponseEntity<Resource> download (@PathVariable("name") String name) throws IOException {
File file = new File(path + name); Path path = Paths.get(file.getAbsolutePath()); ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));
return ResponseEntity.ok().headers(this.headers(name)) .contentLength(file.length()) .contentType(MediaType .parseMediaType("application/octet-stream")) .body(resource); }
@GetMapping("/files") public ResponseEntity<List<String>> getListOfFiles() throws Exception {
return new ResponseEntity<>(service.getListofFiles(), HttpStatus.OK);
}
private HttpHeaders headers(String name) {
HttpHeaders header = new HttpHeaders(); header.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + name); header.add("Cache-Control", "no-cache, no-store, must-revalidate"); header.add("Pragma", "no-cache"); header.add("Expires", "0"); return header;
}}
application.properties
spring.servlet.multipart.enabled=true# Threshold after which files are written to disk.spring.servlet.multipart.file-size-threshold=2KB# Max file size.spring.servlet.multipart.max-file-size=200MB# Max Request Sizespring.servlet.multipart.max-request-size=215MB
Spring Boot Driver
@SpringBootApplicationpublic class Application {
public static void main(String[] args) {
SpringApplication .run(Application.class, args); }}
Project 2: angular-file-upload
package.json
{ "name": "angular-file-upload", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test" }, "private": true, "dependencies": { "@angular/animations": "~13.0.0", "@angular/common": "~13.0.0", "@angular/compiler": "~13.0.0", "@angular/core": "~13.0.0", "@angular/forms": "~13.0.0", "@angular/platform-browser": "~13.0.0", "@angular/platform-browser-dynamic": "~13.0.0", "@angular/router": "~13.0.0", "rxjs": "~7.4.0", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, "devDependencies": { "@angular-devkit/build-angular": "^13.3.9", "@angular/cli": "~13.0.2", "@angular/compiler-cli": "~13.0.0", "@types/jasmine": "~3.10.0", "@types/node": "^12.11.1", "jasmine-core": "~3.10.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.0.3", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "~1.7.0", "typescript": "~4.4.3" }}
file-upload.service.ts
import { Injectable } from '@angular/core';import { HttpClient, HttpRequest, HttpEvent } from '@angular/common/http';import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root'})export class FileUploadService { private baseUrl = 'http://localhost:8080';
constructor(private http: HttpClient) { }
upload(file: File): Observable<HttpEvent<any>> { const formData: FormData = new FormData();
formData.append('file', file);
const req = new HttpRequest('POST', `${this.baseUrl}/upload`, formData, { reportProgress: true, responseType: 'json' });
return this.http.request(req); }
getFiles(): Observable<any> { return this.http.get(`${this.baseUrl}/files`); }}
app.component.ts
import { Component } from '@angular/core';
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']})export class AppComponent { title = 'Angular 13 File Upload';}
import { Component } from '@angular/core';
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']})export class AppComponent { title = 'Angular 13 File Upload';}
app.component.html
<nav class="navbar navbar-dark bg-dark"> <div class="btn-group mx-auto"> <h2 class="text-white">Spring Boot + Angular: File Upload & Download example</h2> </div> </nav><br><br><div class="container" > <app-file-upload></app-file-upload></div>
<nav class="navbar navbar-dark bg-dark"> <div class="btn-group mx-auto"> <h2 class="text-white">Spring Boot + Angular: File Upload & Download example</h2> </div> </nav><br><br><div class="container" > <app-file-upload></app-file-upload></div>
file-upload.component.ts
import { Component, OnInit } from '@angular/core';import { HttpEventType, HttpResponse } from '@angular/common/http';import { Observable } from 'rxjs';import { FileUploadService } from 'src/app/services/file-upload.service';
@Component({ selector: 'app-file-upload', templateUrl: './file-upload.component.html', styleUrls: ['./file-upload.component.css']})export class FileUploadComponent implements OnInit {
selectedFiles?: FileList; currentFile?: File; progress = 0; message = '';
fileInfos?: Observable<any>;
constructor(private uploadService: FileUploadService) { }
ngOnInit(): void { this.fileInfos = this.uploadService.getFiles(); }
selectFile(event: any): void { this.selectedFiles = event.target.files; }
upload(): void { this.progress = 0;
if (this.selectedFiles) { const file: File | null = this.selectedFiles.item(0);
if (file) { this.currentFile = file;
this.uploadService.upload(this.currentFile).subscribe({ next: (event: any) => { if (event.type === HttpEventType.UploadProgress) { this.progress = Math.round(100 * event.loaded / event.total); } else if (event instanceof HttpResponse) { this.message = event.body.message; this.fileInfos = this.uploadService.getFiles(); } }, error: (err: any) => { console.log(err); this.progress = 0;
if (err.error && err.error.message) { this.message = err.error.message; } else { this.message = 'Could not upload the file!'; }
this.currentFile = undefined; } }); }
this.selectedFiles = undefined; } }
}
import { Component, OnInit } from '@angular/core';import { HttpEventType, HttpResponse } from '@angular/common/http';import { Observable } from 'rxjs';import { FileUploadService } from 'src/app/services/file-upload.service';
@Component({ selector: 'app-file-upload', templateUrl: './file-upload.component.html', styleUrls: ['./file-upload.component.css']})export class FileUploadComponent implements OnInit {
selectedFiles?: FileList; currentFile?: File; progress = 0; message = '';
fileInfos?: Observable<any>;
constructor(private uploadService: FileUploadService) { }
ngOnInit(): void { this.fileInfos = this.uploadService.getFiles(); }
selectFile(event: any): void { this.selectedFiles = event.target.files; }
upload(): void { this.progress = 0;
if (this.selectedFiles) { const file: File | null = this.selectedFiles.item(0);
if (file) { this.currentFile = file;
this.uploadService.upload(this.currentFile).subscribe({ next: (event: any) => { if (event.type === HttpEventType.UploadProgress) { this.progress = Math.round(100 * event.loaded / event.total); } else if (event instanceof HttpResponse) { this.message = event.body.message; this.fileInfos = this.uploadService.getFiles(); } }, error: (err: any) => { console.log(err); this.progress = 0;
if (err.error && err.error.message) { this.message = err.error.message; } else { this.message = 'Could not upload the file!'; }
this.currentFile = undefined; } }); }
this.selectedFiles = undefined; } }
}
file-upload.component.html
<div class="row"> <div class="col-8"> <label class="btn btn-default p-0"> <input type="file" (change)="selectFile($event)" /> </label> </div>
<div class="col-4"> <button class="btn btn-success btn-sm" [disabled]="!selectedFiles" (click)="upload()"> Upload </button> </div></div>
<div *ngIf="currentFile" class="progress my-3"> <div class="progress-bar progress-bar-info" role="progressbar" attr.aria-valuenow="{{ progress }}" aria-valuemin="0" aria-valuemax="100" [ngStyle]="{ width: progress + '%' }" > {{ progress }}% </div></div>
<div *ngIf="message" class="alert alert-secondary" role="alert">{{ message }}</div>
<div class="card mt-3"> <div class="card-header">Download the file</div> <ul class="list-group list-group-flush" *ngFor="let file of fileInfos | async" > <li class="list-group-item"> <a href="http://localhost:8080/download/{{ file }}">{{ file }}</a> </li> </ul></div>
<div class="row"> <div class="col-8"> <label class="btn btn-default p-0"> <input type="file" (change)="selectFile($event)" /> </label> </div>
<div class="col-4"> <button class="btn btn-success btn-sm" [disabled]="!selectedFiles" (click)="upload()"> Upload </button> </div></div>
<div *ngIf="currentFile" class="progress my-3"> <div class="progress-bar progress-bar-info" role="progressbar" attr.aria-valuenow="{{ progress }}" aria-valuemin="0" aria-valuemax="100" [ngStyle]="{ width: progress + '%' }" > {{ progress }}% </div></div>
<div *ngIf="message" class="alert alert-secondary" role="alert">{{ message }}</div>
<div class="card mt-3"> <div class="card-header">Download the file</div> <ul class="list-group list-group-flush" *ngFor="let file of fileInfos | async" > <li class="list-group-item"> <a href="http://localhost:8080/download/{{ file }}">{{ file }}</a> </li> </ul></div>
index.html
<!doctype html><html lang="en"><head> <meta charset="utf-8"> <title>SprinBootAngularFileUpload</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="stylesheet" href="https://unpkg.com/bootstrap@4.6.0/dist/css/bootstrap.min.css" /></head><body> <app-root></app-root></body></html>
<!doctype html><html lang="en"><head> <meta charset="utf-8"> <title>SprinBootAngularFileUpload</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="stylesheet" href="https://unpkg.com/bootstrap@4.6.0/dist/css/bootstrap.min.css" /></head><body> <app-root></app-root></body></html>
Download the complete source code - click here
Local Setup and Run the application
Step1: Download or clone the source code from GitHub to a local machine - Click here
Backend
Step 2: mvn clean install
Step 3: Run the Spring Boot application - mvn spring-boot:run
Frontend
Step 4: npm install
Step 5: ng serve --port 8082