Spring Boot 2 + Angular 11 + Preview PDF File

February 16, 2021 No comments Spring Boot Angular Preview PDF

1. Introduction

In this tutorial, we are going to learn how to create Angular application that previews PDF files served by Spring Boot server.

2. Project architecture

The architecture presents as follows:

Angular spring boot pdf viewer

We can distinguish two main layers: the backend and frontend. On the frontend side, we have an Angular application that is responsible for presenting PDF files downloaded from the Spring Boot application server (backend side).

3. Backend side

3.1. Project structure

Let's check the Spring Boot application project structure:

├── java
│   └── com
│       └── frontbackend
│           └── springboot
│               ├── Application.java
│               └── controller
│                   └── PDFController.java
└── resources
    └── application.properties

Project contains the following files:

  • Application - main Spring Boot starter class,
  • PDFController - Spring Rest Controller used for downloading PDF files,
  • application.properties - Spring Boot configuration file.

3.2. Rest Controller

The PDFController class is responsible for downloading PDF files selected by the user. Files are located in the folder whose path is taken from the configuration parameter: pdf.path.


package com.frontbackend.springboot.controller;

import java.io.IOException;
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.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;

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

    @Value("${pdf.path}")
    private String pdfFilesPath;

    @GetMapping("{filename:.+}")
    @ResponseBody
    public ResponseEntity<Resource> downloadFile(@PathVariable String filename) throws IOException {
        Resource resource = new UrlResource(Paths.get(pdfFilesPath)
                                                 .resolve(filename)
                                                 .toUri());

        if (resource.exists() || resource.isReadable()) {
            String contentDisposition = String.format("inline; filename=\"%s\"", resource.getFile()
                                                                                         .getName());
            return ResponseEntity.ok()
                                 .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
                                 .body(resource);
        }

        return ResponseEntity.notFound()
                             .build();
    }
}

This Rest API returns a file content or HTTP 404 code when a file does not exist in the filesystem.

3.3. Spring Boot application class

The main Spring Boot class annotated with @SpringBootApplication is simple, it contains the only main method that will start the server instance.

package com.frontbackend.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

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

3.4. Spring Boot configuration

The application.properties has only one entry with a path to a folder that contains PDF files:

pdf.path=/path/for/pdf/files

4. Frontend side

4.1. Project structure

The angular application was created using ng cli:

To create an Angular project without tests we used:

ng new angular --minimal

The angular application contains the main app component that will be responsible for previewing downloaded PDF files. The structure of the application is as follows:

├── app
│   ├── app.component.css
│   ├── app.component.html
│   ├── app.component.ts
│   ├── app.module.ts
│   └── app-routing.module.ts
├── assets
├── environments
│   ├── environment.prod.ts
│   └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
└── styles.css

4.2. The package.json file

In the package.json file besides the Angular 11 common libraries, we also have ng2-pdf-viewer and a module responsible for previewing PDF files in web projects.

To add the ng2-pdf-viewer module the following command was used:

npm install ng2-pdf-viewer --save
{
  "name": "angular",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "~11.0.9",
    "@angular/common": "~11.0.9",
    "@angular/compiler": "~11.0.9",
    "@angular/core": "~11.0.9",
    "@angular/forms": "~11.0.9",
    "@angular/platform-browser": "~11.0.9",
    "@angular/platform-browser-dynamic": "~11.0.9",
    "@angular/router": "~11.0.9",
    "ng2-pdf-viewer": "^6.3.2",
    "pdfjs-dist": "2.5.207",
    "rxjs": "~6.6.0",
    "tslib": "^2.0.0",
    "zone.js": "~0.10.2"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.1100.7",
    "@angular/cli": "~11.0.7",
    "@angular/compiler-cli": "~11.0.9",
    "@types/node": "^12.11.1",
    "typescript": "~4.0.2"
  }
}

4.3. Angular application component

The AppComponent is responsible for presenting selected PDF file and handling navigation through PDF pages using next/previous buttons.

import { Component } from '@angular/core';
import { environment } from '../environments/environment';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent {
  title = 'angular';

  pdfList: any[] = environment.pdfs;
  pdfDocumentSrc: any = environment.pdfs[0];
  page: number = 1;
  totalPages: number = 0;
  isLoaded: boolean = false;

  afterLoadComplete(pdfData: any) {
    this.totalPages = pdfData.numPages;
    this.isLoaded = true;
  }

  nextPage() {
    this.page++;
  }

  prevPage() {
    this.page--;
  }

  showPdf(pdfFile: any, $event: any) {
    this.pdfList.forEach(pdf => pdf.selected = false);
    pdfFile.selected = true;
    this.pdfDocumentSrc = pdfFile;
    $event.preventDefault();
  }
}

4.4. Application component HTML

The ApplicationComponent will use the following HTML template:

<div>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="/">FrontBackend</a>
        </div>
    </nav>

    <div class="container mt-3">
        <div class="row">
            <div class="col-2">
                <div class="list-group">
                    <a *ngFor="let pdfFile of pdfList" [ngClass]="{'active': pdfFile.selected}" href="#" class="list-group-item list-group-item-action" (click)="showPdf(pdfFile, $event)">{{pdfFile.filename}}</a>
                </div>
            </div>
            <div class="col-10">
                <div *ngIf="isLoaded" style="text-align: center;">
                    <button class="btn btn-primary" (click)="prevPage()" [disabled]="page === 1">Prev</button>
                    <span class="mr-2 ml-2">{{ page }} / {{ totalPages }}</span>
                    <button class="btn btn-primary" (click)="nextPage()" [disabled]="page === totalPages">Next</button>
                </div>

                <pdf-viewer [src]="pdfDocumentSrc"
                            [show-all]="false"
                            [page]="page"
                            (after-load-complete)="afterLoadComplete($event)"
                ></pdf-viewer>
            </div>
        </div>
    </div>
</div>

The pdf-viewer is responsible for presenting a PDF file. This module could also render the PDF, so you could select text from the file on the HTML website.

<pdf-viewer [src]="pdfDocumentSrc"
            [show-all]="false"
            [page]="page"
            (after-load-complete)="afterLoadComplete($event)">
</pdf-viewer>

5. Demo

The application presents the following functionality:

Angular spring boot pdf viewer

6. Conclusion

In this article, we presented a sample Angular application to preview PDF files served by a Spring Boot application server. We used the ng2-pdf-viewer library that is perfect for this and comes with many features like for example rendering PDF files.

As usual code used in this article is allowed for download from our GitHub repository.

{{ message }}

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