Many times we may want to generate PDF files from the template in an Angular 13+ application. For example, we may create a component for Invoice or price estimation and wants to generate a PDF file based on the component template. We will do that in couple of ways in this tutorial.
For this example, we are using Tailwind CSS and you are free to use any other CSS of your choice. Check this post if you want to know How to Add Tailwind CSS to an Angular 13+ Application
Suppose we create a template for invoice with the following HTML
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8" id="invoice" #invoice>
<div class="max-w-3xl mx-auto">
<div class="flex flex-row justify-between m-2 text-xs">
<div class=" bg-gray-100 grow m-1 p-3 rounded basis-1/2">
<h1 class="text-violet-500 text-sm">Invoice from</h1>
<div class="flex flex-row justify-evenly m-1">
<div class=" basis-1/4 ">
<h3>Company</h3>
</div>
<div class="basis-3/4">
<h3>Lorem ipsum dolor sit. </h3>
</div>
</div>
<div class="flex flex-row justify-evenly m-1">
<div class=" basis-1/4 ">
<h3>Address </h3>
</div>
<div class="basis-3/4">
<h3> Lorem, ipsum dolor sit amet consectetur adipisicing elit. Modi aut facere magni accusamus
</h3>
</div>
</div>
</div>
<div class=" bg-gray-100 grow m-1 p-3 rounded basis-1/2">
<h1 class="text-violet-500 text-sm">Invoice To</h1>
<div class="flex flex-row justify-evenly m-1">
<div class=" basis-1/4 ">
<h3>Company</h3>
</div>
<div class="basis-3/4">
<h3>Lorem, ipsum dolor. </h3>
</div>
</div>
<div class="flex flex-row justify-evenly m-1">
<div class=" basis-1/4 ">
<h3>Address </h3>
</div>
<div class="basis-3/4">
<h3> Lorem, ipsum dolor sit amet consectetur adipisicing elit.
</h3>
</div>
</div>
</div>
</div>
<div class="flex flex-col m-5 text-xs">
<div class="-my-2 sm:-mx-6 lg:-mx-8">
<div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
<div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-violet-500">
<tr>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-white uppercase tracking-wider">
Item No</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-white uppercase tracking-wider">
Description</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-white uppercase tracking-wider">
Rate</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-white uppercase tracking-wider">
Quantity</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-white uppercase tracking-wider">
HSN / SAC</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-white uppercase tracking-wider">
Amount</th>
</tr>
</thead>
<tbody>
<tr class="bg-white">
<td class="px-6 py-4 whitespace-nowrap text-xs font-medium text-gray-900">01</td>
<td class="px-6 py-4 text-xs text-gray-500 ">Lorem ipsum dolor sit amet.</td>
<td class="px-6 py-4 whitespace-nowrap text-xs text-gray-500">
99,60,000</td>
<td class="px-6 py-4 whitespace-nowrap text-xs text-gray-500">999</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-xs font-medium">
<a href="#" class="text-indigo-600 hover:text-indigo-900">999999</a>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-xs font-medium">
<a href="#" class="text-indigo-600 hover:text-indigo-900">999999</a>
</td>
</tr>
<tr class="bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-xs font-medium text-gray-900">02</td>
<td class="px-6 py-4 whitespace-nowrap text-xs text-gray-500">Lorem, ipsum dolor.
</td>
<td class="px-6 py-4 whitespace-nowrap text-xs text-gray-500">
99,60,000</td>
<td class="px-6 py-4 whitespace-nowrap text-xs text-gray-500">999</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-xs font-medium">
<a href="#" class="text-indigo-600 hover:text-indigo-900">888888</a>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-xs font-medium">
<a href="#" class="text-indigo-600 hover:text-indigo-900">999999</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<button (click)="generatePDF()">Generate</button>
the output for the above HTML with tailwind CSS may look something like this (without button at the bottom)
Pay attention to the font size we have used inside the code. We have used extra small (xs) and small (sm). When you want to convert your HTML to PDF, pay attention to the font size, as the font size that appears in the web page may not be same inside the PDF document.
With the above HTML inside our component, we now want to generate a PDF file that looks same.
Generating PDF in Angular 13+ Application using jsPDF
In this first example, we will generate the PDF file in Angular 13+ application using a package jsPDF.
first let us install the package jsPDF through npm (–save adds this entry to package.json file)
npm install jspdf --save
The jsPDF package depends on html2canvas package for certain functionality, so lets add that
npm i html2canvas --save
Now, we can import them in our component ts file like this
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
We can now have the following code in our ts file
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
@Component({
selector: 'app-generatepdf',
templateUrl: './generatepdf.component.html',
styleUrls: ['./generatepdf.component.css']
})
export class GeneratepdfComponent implements OnInit {
@ViewChild('invoice') invoiceElement!: ElementRef;
constructor() { }
ngOnInit(): void {
}
public generatePDF(): void {
html2canvas(this.invoiceElement.nativeElement, { scale: 3 }).then((canvas) => {
const imageGeneratedFromTemplate = canvas.toDataURL('image/png');
const fileWidth = 200;
const generatedImageHeight = (canvas.height * fileWidth) / canvas.width;
let PDF = new jsPDF('p', 'mm', 'a4',);
PDF.addImage(imageGeneratedFromTemplate, 'PNG', 0, 5, fileWidth, generatedImageHeight,);
PDF.html(this.invoiceElement.nativeElement.innerHTML)
PDF.save('angular-invoice-pdf-demo.pdf');
});
}
}
In the above code, we are first accessing the element which we want to generate as PDF using ViewChild.
When the user clicks on the button to generate PDF file, we are first converting the UI of the element into an image using html2canvas package. Pay attention to the value we have given to the scale property which is 3 in the above example. The larger the number, the larger the size of the image generated. Generating image without this option, may result in creating lower size image and subsequently blurred or low quality PDF file. So, use this option as per your requirements.
We are then passing the generated image to the jsPDF class, which simply creates a PDF file with the passed image in it.
With the above code in place, the output of the PDF file generated when the user clicks on the button opened in chrome browser may look like this
Though we can use the above method to generated PDF files, the problems with this method is, the size of the generated PDF file can be big. Another problem is, since we are first creating an image (almost like taking a screen shot of the element UI) and passing to the jsPDF, the text inside the PDF file cannot be selected. If your intended requirement is this and if you are not worried about the size of the file, then you can use this method. One advantage of this method is, it do not consume any bandwidth because we are generating the document at client side.
Another way to generate the PDF document from an Angular application is to generate the file at the server and send it to the client.
Generating PDF in Angular 13+ & Node JS Application using html-pdf-node
How this method works: We need to pass entire HTML along with CSS to the PDF generator package to let it create the PDF file based on the passed HTML & CSS.
If we run the Angular application in normal method, the UI will be created on the client side. In-order to access the entire component HTML and CSS, we need to implement Server Side Rendering (SSR) using Angular Universal. We can do the with the following command
ng add @nguniversal/express-engine
then we can run the application with the following command
npm run dev:ssr
Now, let us create Node JS server with express package to run the server and html-pdf-node package to create PDF file.
const express = require('express');
const app = express();
let fs = require('fs');
let html_to_pdf = require('html-pdf-node');
app.get('/', (req, res) => {
res.send('Node JS running at port number 3000')
})
app.get('/generate', (req, res) => {
let options = { format: 'A4', printBackground: true };
let file = { url: "http://localhost:4200/invoice" };
html_to_pdf.generatePdf(file, options).then(pdfBuffer => {
fs.writeFileSync('howtojs-angular-html-pdf-node-demo.pdf', pdfBuffer)
res.send({ genratedPDFLink: 'link to your generated pdf file to download' })
});
})
app.listen(3000);
The advantage of using html-pdf-node package is, we can pass a static url and the package gets all the HTML and CSS contents from that url and creates a PDF file. If you want to use any other package to create PDF which accepts HTML, you need to first gets the contents using a package such as request(now deprecated but still very popular) and pass it to the PDF generator package.
In the above example, we are passing http://localhost:4200/invoice url to it. Since the package gets the contents of the entire url, make sure that you are rendering only the HTML and CSS that you want inside the PDF at this url. If you have any navigation bar or footers, even they will be included in the generated PDF file. You can implement the strategy to show only the required HTML and CSS in that url based on your application requirement, for our example, we can hide certain parts of the template based on the url.
In app component ts file, we can access the current page url and check if that is invoice page or not
import { Component } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'howtojs.io';
isInvoicePage: boolean = false;
constructor(private router: Router) {
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
let urlSegments = event.url.split('/');
if (urlSegments[1] == 'invoice') {
this.isInvoicePage = true;
} else {
this.isInvoicePage = false;
}
}
})
}
}
We can have our template like this
<div *ngIf="!isInvoicePage">
<a routerLink="">Home</a>
<a routerLink="invoice"> Invoice </a>
</div>
<router-outlet></router-outlet>
<button *ngIf="!isInvoicePage" (click)="generatePDF()">Generate PDF</button>
When the user clicks on the button, you can send a http request to the server to create the PDF file
generatePDF() {
this.httpClient.get('http://localhost:3000/generate').subscribe((data) => {
console.log(data)
})
}
In the example above, the url based on which we are generating the PDF file is public, that means, its accessible to anyone.
There are different ways to secure this url, and one simple way can be like this. You pass a secret token to the server when you send a request to generate the file. Include that token in the url requested from the server. Implement a routing guard to check that token.
Do let us know in the comments below if you want us to show an example for the same.
The size of the PDF file created with the first method was in MB’s (you can lower it by reducing the image clarity passed to jsPDF) . The size of the file with the second method was just around 50KB. You can now pass this newly generated PDF file url to the client in the response to download.
it is possible to have an example of this code with securate path of pdf