Spring Boot + Angular + Download Excel, PDF and CSV File

April 18, 2021 No comments Spring Boot Angular Download PDF

1. Introduction

In this article we are going to present how to download files like PDF, Excel and CSV using Spring Boot on the backend and Angular on the frontend. Files will be downloaded from the filesystem and served using REST controller.

2. Architecture

Let's start with the architecture of the application:

Angular spring boot download CSV pdf excel architecture

There are two main layers: the frontend and backend side. The frontend side is used to present a user interface with links to files. The backend will be responsible for serving files using REST endpoints handled by the Spring Boot controller.

3. Backend

The backend side is handled by the Spring Boot application, which will serve files that could be directly downloaded from the browser.

3.1. The structure of the Spring Boot application

├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── frontbackend
│   │   │           └── springboot
│   │   │               ├── Application.java
│   │   │               ├── controller
│   │   │               │   └── FileController.java
│   │   │               └── service
│   │   │                   └── FileService.java
│   │   └── resources
│   │       └── application.properties

The structure contains the following classes:

  • Application - which is a main Spring Boot class used for starting web application,
  • FileController - Spring Rest Controller class used for handling HTTP requests,
  • FileService - the Service class responsible for downloading files,
  • application.properties - the configuration file.

3.2. Spring Controller class

The Spring controller class will handle the HTTP GET requests:

package com.frontbackend.springboot.controller;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.frontbackend.springboot.service.FileService;

@CrossOrigin(origins = "http://localhost:4200")
@RestController
@RequestMapping("api/files")
public class FileController {

    private final FileService fileService;

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

    @GetMapping("{filename:.+}")
    @ResponseBody
    public ResponseEntity<Resource> downloadFile(@PathVariable String filename) throws IOException {
        Resource file = fileService.download(filename);
        Path path = file.getFile()
                        .toPath();

        return ResponseEntity.ok()
                             .header(HttpHeaders.CONTENT_TYPE, Files.probeContentType(path))
                             .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"")
                             .body(file);
    }
}

This class is annotated with @RestController stereotype that marks this class as REST controller. The next annotation @RequestMapping was used to provide the base URL for endpoints used in this controller.

In order to download files we need to use a special HTTP header: Content-Disposition - this header indicating that the file should be downloaded (the 'Save as' dialog should be opened on the browser).

3.3. Service class

The service class was introduced to separate logic that is responsible for downloading files with REST controller:

package com.frontbackend.springboot.service;

import java.net.MalformedURLException;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;

@Service
public class FileService {

    @Value("${files.path}")
    private String filesPath;

    public Resource download(String filename) {
        try {
            Path file = Paths.get(filesPath)
                             .resolve(filename);
            Resource resource = new UrlResource(file.toUri());

            if (resource.exists() || resource.isReadable()) {
                return resource;
            } else {
                throw new RuntimeException("Could not read the file!");
            }
        } catch (MalformedURLException e) {
            throw new RuntimeException("Error: " + e.getMessage());
        }
    }
}

For the sake of simplicity, this service just returns files or throws an exception when the file will not be found.

3.4. Configuration file

The application.properties contains the path to files that we will want to download on the frontend side:

files.path=/home/path/to/files

4. Frontend

4.1. Generate Angular Project

The Angular project was generated using CLI and ng command:

To create an empty Angular project we used:

> ng new angular --minimal

To create a file component:

> ng g c components/files

To create a service:

> ng g s services/download

4.2. The structure of the Angular application

The generated project has the following structure:

├── package.json
├── package-lock.json
├── README.md
├── src
│   ├── app
│   │   ├── app.component.html
│   │   ├── app.component.ts
│   │   ├── app.module.ts
│   │   ├── app-routing.module.ts
│   │   ├── components
│   │   │   └── files
│   │   │       ├── files.component.html
│   │   │       └── files.component.ts
│   │   └── services
│   │       └── download.service.ts
│   ├── assets
│   ├── environments
│   │   ├── environment.prod.ts
│   │   └── environment.ts
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   ├── polyfills.ts
│   └── styles.css
├── tsconfig.app.json
└── tsconfig.json

4.3. Angular files component

Files component was created to present a list of files that we want to download:

import { Component } from '@angular/core';
import { DownloadService } from '../../services/download.service';
import { saveAs } from 'file-saver';

@Component({
  selector: 'app-files',
  templateUrl: './files.component.html'
})
export class FilesComponent {

  constructor(private downloadService: DownloadService) {
  }

  downloadFile(filename: string): void {
    this.downloadService
      .download(filename)
      .subscribe(blob => saveAs(blob, filename));
  }
}

The HTML document related to this FilesComponent has the following structure:

<div class="list-group">

    <a href="#" (click)="downloadFile('sample.pdf')">Download PDF</a><br/>

    <a href="#" (click)="downloadFile('sample.xlsx')">Download Excel</a><br/>

    <a href="#" (click)="downloadFile('sample.csv')">Download CSV</a><br/>

</div>

4.4. Angular download service

The DownloadService is used Angular HttpClient to communicate with the Spring Boot application server. This component is responsible for downloading files:

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

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

  constructor(private http: HttpClient) {
  }

  download(file: string | undefined): Observable<Blob> {
    return this.http.get(`${environment.baseUrl}/files/${file}`, {
      responseType: 'blob'
    });
  }
}

5. Demo

The application shows the following functionality:

Angular spring boot download CSV pdf excel file

As you can see files are downloaded right away, without prompting about opening them. This behavior was achieved because we used a special HTTP header Content-Disposition with the attachment value. If you want to preview PDF File instead of downloading you should also use Content-Disposition header but with inline value.

6. Conclusion

In this tutorial, we show a simple example of how to download files using Spring Boot and Angular.

As usual, the code used in this tutorial is available in our GitHub repository.

{{ message }}

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