How to create a PDF file in Ionic apps using PouchDB

How to create a PDF file in Ionic apps using PouchDB

In Short:

You will learn how to create a PDF file based on data from PouchDB and print the file with cordova-plugin-printer.

Pre-Condition:

For creating a PouchDB instance and inserting data, check out the following tutorial:

Ionic 5 Background Geolocation Tracking with PouchDB
Step 1Create a new Ionic project. npm install -g ionic@latestionic start ionic4-background-geo-pouchdb blank Step 2Install plugin and dependencies. // geolocation trackingionic cordova plugin add @mauron85/cordova-plugin-background-geolocationnpm install --save @ionic-native/background-geolo…

How it works

Step 1
Create an ionic angular app or use an existing one. For creating a new one you can use the "tabs" template.

ionic start myApp tabs

Step 2
Install the following dependencies

npm install pdfmake --save

ionic cordova plugin add cordova-plugin-file
npm install @ionic-native/file

ionic cordova plugin add cordova-plugin-printer
npm install @ionic-native/printer

Step 3
Import the dependencies installed before and create a global pdfMake object

import {Printer} from '@ionic-native/printer/ngx';
import {File} from '@ionic-native/file/ngx';

@NgModule({
    ...
    providers: [
    	File,
        Printer,...
	]
})
import pdfMake from 'pdfmake/build/pdfmake';
import pdfFonts from 'pdfmake/build/vfs_fonts';

import { File } from '@ionic-native/file/ngx';
import {CouchApiService} from '../providers/couch-api.service';

pdfMake.vfs = pdfFonts.pdfMake.vfs;
...
export class OurPage {
	pdfObj = null;

  constructor(private couchApiService: CouchApiService,
              private file: File,
              private printer: Printer) {
  }
}

Step 4
Create a method for a PDF layout. For displaying the stored data of our PouchDB a HTML table is used.

The stored data will be added to the template in an additional method (next step).

private createTemplate() {
        const docDefinition = {
            content: [
                {text: 'Our Title', style: 'header'},
                {text: new Date().toLocaleString(), alignment: 'right'},

                {text: 'Our Subtitle ', style: 'subheader'},

                {text: '', style: 'story', margin: [0, 20, 0, 20]},
                {
                    table: {
                        // headers are repeated if the table spans over 							multiple pages
                        // you can declare how many rows should be 									treated as headers e.g. 1
                        headerRows: 1,
                        widths: [150, '*', '*', '*', 150],
                        body: [
                            [{text: 'Date', style: 'tableHeader'}, {text: 'Table Name 2', style: 'tableHeader'}, {text: 'Table Name 3', style: 'tableHeader'}, {text: 'Table Name 4', style: 'tableHeader'}, {text: 'Table Name 5', style: 'tableHeader'}]
                        ]
                    },
                    layout: {
                    // we will specify a layout for our table
                        fillColor: (rowIndex, node, columnIndex) => {
                            return (rowIndex % 2 === 0) ? '#CCCCCC' : null;
                        },
                        hLineWidth: (i, node) => {
                            return (i === 0 || i === node.table.body.length) ? 2 : 1;
                        },
                        vLineWidth: (i, node) => {
                            return (i === 0 || i === node.table.widths.length) ? 2 : 1;
                        },
                    }
                },
                {text: '©AppIT Online - ' + new Date().getFullYear(), style: 'story', margin: [0, 0, 0, 20]},
            ],
            styles: {
            //additional styles
                header: {
                    fontSize: 18,
                    bold: true,
                },
                subheader: {
                    fontSize: 14,
                    bold: true,
                    margin: [0, 15, 0, 0]
                },
                story: {
                    italic: true,
                    alignment: 'center',
                    width: '50%',
                },
                tableHeader: {
                    bold: true,
                    fontSize: 13,
                    color: 'black'
                }
            }
        };
        return docDefinition;
    }

Step 5
After creating the PDF template the PouchDB content needs to be added to the HTML table.

private generatePDF(docDefinition: any) {
        this.couchApiService.read('our_database_name')
            .then((entries: any) => {
                for (const entry of entries) {
                    docDefinition.content[4].table.body.push(
                    [new Date(entry.time).toLocaleString(), 
                    entry.column2, 
                    entry.column3, 
                    entry.column4, 
                    entry.column5 || ''
                    ]);
                }

                this.pdfObj = pdfMake.createPdf(docDefinition);

                this.pdfObj.getBuffer((buffer) => {
                    const blob = new Blob([buffer], 
                    {type: 'application/pdf'});
                    
                    //after creating the pdf we will write on the file storage
                    this.getStoragePath()
                        .then((url) => {
                            this.file.writeFile(url, 
                            'appit-filename.pdf', blob, {replace: true})
                                .then(() => {
                                	//after that we will print the pdf
                                    this.print();
                                }).catch((error) => {
                                console.log(error);
                            });
                        })
                        .catch((error) => {
                            console.log(error);
                        });
                });

            })
            .catch((error) => {
                console.log(error);
            });
    }

Step 6
Then a method for printing the PDF is needed. Therefore the "cordova-plugin-printer" is used.

 private async print() {
        this.printer.isAvailable()
            .then(() => {
                this.getStoragePath().then( (url) => {
                    const options: PrintOptions = {
                        name: 'Name',
                        duplex: true
                    };
                    this.printer.print(
                    	url + '/' + 'appit-filename.pdf',
                    	options
                    );
                })
                    .catch((error) => {
                        console.log(error);
                    });

            })
            .catch((error) => {
                console.log(error);
            });
    }

Step 7
In the steps before the "getStoragePath" method was used for receiving the file path, where the file will be persisted.

    private getStoragePath() {
        const file = this.file;
        return this.file.resolveDirectoryUrl(this.file.dataDirectory)
        .then( (directoryEntry) => {
        	return file.getDirectory(
            	directoryEntry, 
            	'our_root_folder', 				
                {
                    create: true,
                    exclusive: false
                })
            .then( () => {
                return directoryEntry.nativeURL + 'our_root_folder/';
            });
        });
    }

Step 8
Finally lets put it all together

//UI button
<button class="btn btn-primary my-4" type="button" (click)="exportPDF()">
      Print PDF
</button>
    
// our-page.ts
async exportPDF() {
    const docDefinition = this.createTemplate();
    this.generatePDF(docDefinition);
}

Step 9 (Optional)
The CouchAPI Service is explained in this tutorial:
https://blog.appit-online.de/background-geolocation-tracking-with-ionic-4/

The "read" method is defined as follows

 read(name) {
        const db = new PouchDB(name + '.db', 
        	{
        		auto_compaction: true, 
        		adapter: 'cordova-sqlite', 
        		iosDatabaseLocation: 'Library',
        		androidDatabaseImplementation: 2
        	}
        );

        return new Promise((resolve, reject) => {
            db.allDocs({
                include_docs: true
            }).then((result) => {

                this.data = [];
                result.rows.map((row) => {
                    if (row.doc._id) {
                        this.data.push(row.doc);
                    }
                });

                resolve(this.data);
            }).catch((error) => {
                console.log(error);
                reject(error);
            });
        });
    }