Spring Boot 2 + Angular 11 + Upload File Example

February 27, 2021 No comments Spring Boot Angular Upload File

1. Introduction

In this tutorial we are going to present how to create an application for uploading a single file into filesystem using Spring Boot 2 and Angular 11.

2. Architecture

Let's take a look at the architecture:

Spring boot upload single file

Here we have a general division into the backend and frontend side. The Angular in version 11 will be responsible for building the UI and the Spring Boot will handle uploaded files and store them into the filesystem. The communication between modules will be RESTful.

3. Spring Boot application

On the backend side, we have a Spring Boot application server that provides a single endpoint /files for handling multipart/form-data requests.

3.1. Project structure

The backend application will have the following structure:

├── main
│   ├── java
│   │   └── com
│   │       └── frontbackend
│   │           └── springboot
│   │               ├── Application.java
│   │               ├── controller
│   │               │   └── FilesController.java
│   │               ├── exceptions
│   │               │   ├── FileUploadException.java
│   │               │   └── RestExceptionHandler.java
│   │               ├── model
│   │               │   └── UploadResponseMessage.java
│   │               └── service
│   │                   └── FileService.java
│   └── resources
│       └── application.properties

Let's briefly explain used components:

3.2. Rest Controller

The FilesController class is responsible for handling multipart requests.

package com.frontbackend.springboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.frontbackend.springboot.model.UploadResponseMessage;
import com.frontbackend.springboot.service.FileService;

@RestController
@RequestMapping("files")
public class FilesController {

    private final FileService fileService;

    @Autowired
    public FilesController(FileService fileService) {
        this.fileService = fileService;
    }

    @PostMapping
    public ResponseEntity<UploadResponseMessage> uploadFile(@RequestParam("file") MultipartFile file) {
        fileService.save(file);

        return ResponseEntity.status(HttpStatus.OK)
                             .body(new UploadResponseMessage("Uploaded the file successfully: " + file.getOriginalFilename()));
    }
}

In this class, we used the MultipartFile object that is a representation of an uploaded file received in a multipart request. When the user uploaded a file, at the first step it will be stored in a temporary location in the filesystem and the MultipartFile points to that file. This temporary file will be removed at the end of request processing.

3.3. Service class

The FileService is responsible for saving uploaded files in the filesystem:

package com.frontbackend.springboot.service;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.frontbackend.springboot.exceptions.FileUploadException;

@Service
public class FileService {

    @Value("${upload.path}")
    private String uploadPath;

    public void save(MultipartFile file) throws FileUploadException {
        try {
            Path root = Paths.get(uploadPath);
            Path resolve = root.resolve(file.getOriginalFilename());
            if (resolve.toFile()
                       .exists()) {
                throw new FileUploadException("File already exists: " + file.getOriginalFilename());
            }
            Files.copy(file.getInputStream(), resolve);
        } catch (Exception e) {
            throw new FileUploadException("Could not store the file. Error: " + e.getMessage());
        }
    }
}

Path, where we will store all uploaded files, is saved in application parameters in property upload.path. If a file with the same name exists in that location the new FileUploadException will be thrown.

3.4. Spring Boot application class

In the Spring Boot application class we configured CORS and upload size limits just to show it could be implemented here:

package com.frontbackend.springboot;

import javax.servlet.MultipartConfigElement;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.util.unit.DataSize;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setMaxFileSize(DataSize.ofKilobytes(512));
        factory.setMaxRequestSize(DataSize.ofKilobytes(512));
        return factory.createMultipartConfig();
    }

    @Bean
    WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/files")
                        .allowedOrigins("http://localhost:4200");
            }
        };
    }
}

3.5. Application configuration file

In the application properties file we have a single entry with the path to the folder where we will save all files:


upload.path=/path/to/upload/folder

4. Angular application

To generate the basic structure of the Angular application we will use CLI:

We used the following command:

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

Next, we would like to create a service and component for uploading files:

ng g s services/upload-file
ng g c components/upload-file

4.1. Web project structure

The generated Angular application project has the following structure:

├── app
│   ├── app.component.html
│   ├── app.component.ts
│   ├── app.module.ts
│   ├── components
│   │   └── upload-file
│   │       ├── upload-file.component.html
│   │       └── upload-file.component.ts
│   └── services
│       └── upload-file.service.ts
├── assets
├── environments
│   ├── environment.prod.ts
│   └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
└── styles.css

In this structure we could distinguished:

4.2. Upload file service

The UploadFileService will use the HTTPClient library to send multipart requests to the backend.

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

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

  constructor(private http: HttpClient) {
  }

  upload(file: File): Observable<HttpEvent<any>> {
    const formData: FormData = new FormData();

    formData.append('file', file);

    const req = new HttpRequest('POST', `${environment.baseUrl}/files`, formData, {
      reportProgress: true,
      responseType: 'json'
    });

    return this.http.request(req);
  }
}

The component used FormData is a special data structure used to save key/value pairs. It uses the same format a form would use with the encoding set to multipart/form-data.

4.3. Upload file component

The UploadFileComponent will be our main component responsible for interactions with the end-user.

import { Component, OnInit } from '@angular/core';
import { UploadFileService } from '../../services/upload-file.service';
import { HttpEventType, HttpResponse } from '@angular/common/http';

@Component({
  selector: 'app-upload-file',
  templateUrl: './upload-file.component.html',
  styles: []
})
export class UploadFileComponent implements OnInit {

  selectedFiles?: FileList;
  currentFile?: File;
  message = '';
  errorMsg = '';

  constructor(private uploadService: UploadFileService) {
  }

  ngOnInit(): void {
  }

  selectFile(event: any): void {
    this.selectedFiles = event.target.files;
  }

  upload(): void {
    this.errorMsg = '';

    if (this.selectedFiles) {
      const file: File | null = this.selectedFiles.item(0);

      if (file) {
        this.currentFile = file;

        this.uploadService.upload(this.currentFile).subscribe(
          (event: any) => {
            if (event.type === HttpEventType.UploadProgress) {
              console.log(Math.round(100 * event.loaded / event.total));

            } else if (event instanceof HttpResponse) {
              this.message = event.body.responseMessage;
            }
          },
          (err: any) => {
            console.log(err);

            if (err.error && err.error.responseMessage) {
              this.errorMsg = err.error.responseMessage;
            } else {
              this.errorMsg = 'Error occurred while uploading a file!';
            }

            this.currentFile = undefined;
          });
      }

      this.selectedFiles = undefined;
    }
  }
}

The selectFile method will be called then the user selects the file in the input field.

selectFile(event: any): void {
    this.selectedFiles = event.target.files;
}

On upload file action the upload(): function will be called. It uses FileService to send multipart data to the backend side. When the parameter reportProgress: true, is set in HTTPClient we could present percent of uploaded file:

if (event.type === HttpEventType.UploadProgress) {
    console.log(Math.round(100 * event.loaded / event.total));
}

In component HTML we have a single input with special type="file". Button Upload file is used to send file data to Spring Boot server:

<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 file
        </button>
    </div>
</div>

<div *ngIf="message" class="alert alert-success" role="alert">{{ message }}</div>
<div *ngIf="errorMsg" class="alert alert-danger" role="alert">{{ errorMsg }}</div>

4.4. Application module

In Application module we import HTTPClientModule used in FileService and declare two components:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { UploadFileComponent } from './components/upload-file/upload-file.component';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent,
    UploadFileComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

5. Demo

The following screencast presents uploading file functionality:

Spring boot upload single file

6. Conclusion

In this tutorial, we presented how to build a simple application for uploading a file using Angular 11 on the frontend side and Spring Boot 2 on the backend.

As usual code used in this article is available in our GitHub repository.

{{ message }}

{{ 'Comments are closed.' | trans }}

4617作文网新生宝宝起小名大全现代周公解梦大全八字算命姻缘配对免费柴姓起名大全女孩起名缺水缺木周公解梦梦别人打架周易名字名字打分周易排盘软件下载周易八字免费预测起饮品名字合适起名的词黄姓起名字大全免费被蛇咬手 周公解梦24周胎儿容易缺氧吗梦见解大便是什么意思取名称周易给汽车起个名字大全周公解梦 蜈蚣五行属火的字周易取名周易生辰八字起名测名打分免费搬家公司起名大全卢姓氏女孩起名大全根据周易八卦取名卢姓氏女宝宝起名周易公司取名打分测试免费七画起名常用吉祥清朝皇帝起名字规则家字辈怎么起名驻马店市周易研究会生肖鼠的宝宝起名淀粉肠小王子日销售额涨超10倍罗斯否认插足凯特王妃婚姻让美丽中国“从细节出发”清明节放假3天调休1天男子给前妻转账 现任妻子起诉要回网友建议重庆地铁不准乘客携带菜筐月嫂回应掌掴婴儿是在赶虫子重庆警方辟谣“男子杀人焚尸”国产伟哥去年销售近13亿新的一天从800个哈欠开始男孩疑遭霸凌 家长讨说法被踢出群高中生被打伤下体休学 邯郸通报男子持台球杆殴打2名女店员被抓19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警两大学生合买彩票中奖一人不认账德国打算提及普京时仅用姓名山西省委原副书记商黎光被逮捕武汉大学樱花即将进入盛花期今日春分张家界的山上“长”满了韩国人?特朗普谈“凯特王妃P图照”王树国3次鞠躬告别西交大师生白宫:哈马斯三号人物被杀代拍被何赛飞拿着魔杖追着打315晚会后胖东来又人满为患了房客欠租失踪 房东直发愁倪萍分享减重40斤方法“重生之我在北大当嫡校长”槽头肉企业被曝光前生意红火手机成瘾是影响睡眠质量重要因素考生莫言也上北大硕士复试名单了妈妈回应孩子在校撞护栏坠楼网友洛杉矶偶遇贾玲呼北高速交通事故已致14人死亡西双版纳热带植物园回应蜉蝣大爆发男孩8年未见母亲被告知被遗忘张立群任西安交通大学校长恒大被罚41.75亿到底怎么缴沈阳一轿车冲入人行道致3死2伤奥运男篮美国塞尔维亚同组周杰伦一审败诉网易国标起草人:淀粉肠是低配版火腿肠外国人感慨凌晨的中国很安全男子被流浪猫绊倒 投喂者赔24万杨倩无缘巴黎奥运男子被猫抓伤后确诊“猫抓病”春分“立蛋”成功率更高?记者:伊万改变了国足氛围奥巴马现身唐宁街 黑色着装引猜测

4617作文网 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化