A comprehensive step by step tutorial on Ionic 4, Angular 6 and Cordova export and view pdf file using `dom-to-image`, jSPDF and File Opener. This Ionic 4 Angular 6 tutorial starts by displaying an invoice on the page with export to PDF file button. If you click the button, the Ionic 4 Angular 6 app will generate PDF file then view it in the File Opener.
Table of Contents:
- Create a New Ionic 4 and Angular 6 App
- Install `dom-to-image`, jSPDF and File Opener
- Create Invoice on the Ionic 4 Page
- Export Page to PDF and Preview
- Test Run The Ionic 4 and Angular 6 Application on Device
The jSPDF is a Javascript library that converts specific or selected HTML page to the PDF file. The HTML convert to the Image first by dom-to-image library then jSPDF create a PDF file from that image.
The following tools, frameworks, and modules are required for this tutorial:
- Node.js
- Ionic 4 beta
- Angular 6
- Cordova
- Terminal or Node.js Command Line
- IDE or Text Editor
Before moving to the steps, make sure you have installed the latest Node.js and Ionic 4. To check it, type this command in the terminal or Node.js command line.
node -v
v8.11.4
npm -v
5.6.0
ionic -v
ionic CLI 4.2.0
cordova -v
8.1.1
Update Ionic Framework and Cordova to the latest versions.
sudo npm i -g ionic
sudo npm i -g cordova
Create a New Ionic 4 and Angular 6 App
To create a new Ionic 4 / Angular 6 app, type this command.
ionic start ionic-pdf blank --type=angular
You will see questions during the installation, just type `N` for now. Next, go to the newly created app folder.
cd ./ionic-pdf
For sanitizing, run the app on the browser for the first time to make sure everything working properly.
ionic serve -l
Type `Y` if asked to install `@ionic/lab`. Now, the browser will open automatically then you will see this Ionic 4 Lab page.
Install `dom-to-image`, jSPDF and File Opener
To install `dom-to-image` Javascript library, type this commands.
npm install dom-to-image --save
npm install @types/dom-to-image --save
To install jSPDF Javascript library, type this commands.
npm install jspdf --save
npm install @types/jspdf --save
Now, `dom-to-image` and JSDF Javascript library are ready to use. Next, we have to install and configure Ionic 4 Cordova Native File Opener plugin by type this commands first.
ionic cordova plugin add cordova-plugin-file-opener2
npm install --save @ionic-native/[email protected]
ionic cordova plugin add cordova-plugin-file
npm install --save @ionic-native/[email protected]
Next, open and edit `src/app/app.module.ts` then add this import of the FileOpener (@ionic-native/file-opener/ngx).
import { FileOpener } from '@ionic-native/file-opener/ngx';
Add that module to `@NgModule` providers.
providers: [
StatusBar,
SplashScreen,
FileOpener,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
Now, the File Opener plugin is ready to use on your page.
Create Invoice on the Ionic 4 Page
As we mention at the beginning of this article, we have to create an invoice page that printable or exportable to PDF file. Open and edit an existing `src/app/home/home.page.html` then replace all HTML tags with this <ion-content> that contain the tables of the invoice.
<ion-header>
<ion-toolbar>
<ion-title>
Invoice
</ion-title>
<ion-buttons slot="primary">
<ion-button color="primary" (click)="exportPdf()">
<ion-icon slot="icon-only" name="print"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content padding>
<div class="printable-content">
<div class="printable-area" id="printable-area">
<table class="header-table">
<tr>
<td width="40%" class="title"><img src="https://www.djamware.com/assets/djamware-logo-78bf8a4819aa6df9df518daaa5fe70e0.png" alt="logo" height="40px" /></td>
<td width="60%">
<table width="100%">
<tr>
<td width="50%">+677-12332-1222</td>
<td width="50%">Your Address 1st</td>
</tr>
<tr>
<td>[email protected]</td>
<td>City, State, Country</td>
</tr>
<tr>
<td>yourwebsite.com</td>
<td>Post Code</td>
</tr>
</table>
</td>
</tr>
</table>
<h1>Invoice</h1>
<table class="subheader-table">
<tr>
<td width="30%">
<dt>Billed To:</dt>
<dd>Client Name</dd>
<dd>Address 1st Line</dd>
<dd>City, State, Country</dd>
<dd>Post Code</dd>
</td>
<td width="30%">
<dt>Invoice Number:</dt>
<dd>009223-222</dd>
</td>
<td width="40%">
<dt>Invoice Total:</dt>
<dd>$6.600</dd>
</td>
</tr>
</table>
<h3>Invoice Details</h3>
<table class="detail-table">
<tr>
<th width="50%">Description</th>
<th width="20%">Unit Price</th>
<th width="10%">Qty</th>
<th width="20%">Amount</th>
</tr>
<tr>
<td>Apple Macbook Pro Retina 2018</td>
<td>$4.500</td>
<td>1</td>
<td>$4.500</td>
</tr>
<tr>
<td>Apple Mouse Bluetooth</td>
<td>$500</td>
<td>1</td>
<td>$500</td>
</tr>
<tr>
<td>Apple Cinema Display</td>
<td>$1.000</td>
<td>1</td>
<td>$1.000</td>
</tr>
</table>
<table class="footer-table">
<tr>
<td width="80%">Sub Total: </td>
<td width="20%">$6.000</td>
</tr>
<tr>
<td>Tax: </td>
<td>$600</td>
</tr>
</table>
</div>
</div>
</ion-content>
Next, open and edit `src/app/home/home.page.scss` then add these lines of SASS codes to make Invoice looks like real Invoice.
ion-content {
background-color: #c0c0ff;
.printable-content {
margin: 16px;
overflow-x: scroll;
overflow-y: scroll;
background-color: #ffffff;
.printable-area {
width: 595px;
height: 540px;
.header-table {
background-color: #18AC52;
margin: 2%;
width: 96%;
color: #ffffff;
th, td {
text-align: left;
padding: 5px;
}
.title {
padding: 5px 20px;
font-size: 20px;
font-weight: bold;
text-transform: uppercase;
}
}
h1, h3 {
margin-left: 10px;
}
h3 {
margin-top: 20px;
}
.subheader-table {
margin: 2%;
width: 96%;
td {
vertical-align: top;
dt {
font-size: 11px;
color: #999;
margin-bottom: 5px;
}
dd {
margin: 0;
}
}
td:last-child {
text-align: right;
dd {
font-size: 28px;
font-weight: bold;
}
}
}
.detail-table {
margin: 2%;
width: 96%;
border-top: 2px solid #CCC;
border-bottom: 2px solid #CCC;
th, td {
padding: 5px;
}
th {
border-bottom: 2px solid #CCC;
}
td:nth-child(2), td:nth-child(4) {
text-align: right;
}
td:nth-child(3) {
text-align: center;
}
}
.footer-table {
margin: 2%;
width: 96%;
td {
padding: 5px;
text-align: right;
font-weight: bold;
}
}
}
}
}
Export Page to PDF and Preview
This time to make the Invoice exported to PDF file then preview using Ionic 4 file opener. Open and edit `src/app/home/home.page.ts` then add these imports of LoadingController, jsPDF, domtoimage, File, IWriteOptions, and FileOpener.
import { LoadingController } from '@ionic/angular';
import * as jsPDF from 'jspdf';
import domtoimage from 'dom-to-image';
import { File, IWriteOptions } from '@ionic-native/file/ngx';
import { FileOpener } from '@ionic-native/file-opener/ngx';
Inject above modules to the constructor.
constructor(public loadingCtrl: LoadingController,
private file: File,
private fileOpener: FileOpener) {
}
Add a variable for loading controller before the constructor.
loading: any;
Add a function for presenting loading controller below the constructor.
async presentLoading(msg) {
const loading = await this.loadingController.create({
message: msg
});
return await loading.present();
}
Add a function to convert the selected HTML page to the Image, create PDF from that image, and preview the PDF file.
exportPdf() {
this.presentLoading('Creating PDF file...');
const div = document.getElementById("printable-area");
const options = { background: "white", height: div.clientWidth, width: div.clientHeight };
domtoimage.toPng(div, options).then((dataUrl)=> {
//Initialize JSPDF
var doc = new jsPDF("p","mm","a4");
//Add image Url to PDF
doc.addImage(dataUrl, 'PNG', 20, 20, 240, 180);
let pdfOutput = doc.output();
// using ArrayBuffer will allow you to put image inside PDF
let buffer = new ArrayBuffer(pdfOutput.length);
let array = new Uint8Array(buffer);
for (var i = 0; i < pdfOutput.length; i++) {
array[i] = pdfOutput.charCodeAt(i);
}
//This is where the PDF file will stored , you can change it as you like
// for more information please visit https://ionicframework.com/docs/native/file/
const directory = this.file.dataDirectory ;
const fileName = "invoice.pdf";
let options: IWriteOptions = { replace: true };
this.file.checkFile(directory, fileName).then((success)=> {
//Writing File to Device
this.file.writeFile(directory,fileName,buffer, options)
.then((success)=> {
this.loading.dismiss();
console.log("File created Succesfully" + JSON.stringify(success));
this.fileOpener.open(this.file.dataDirectory + fileName, 'application/pdf')
.then(() => console.log('File is opened'))
.catch(e => console.log('Error opening file', e));
})
.catch((error)=> {
this.loading.dismiss();
console.log("Cannot Create File " +JSON.stringify(error));
});
})
.catch((error)=> {
//Writing File to Device
this.file.writeFile(directory,fileName,buffer)
.then((success)=> {
this.loading.dismiss();
console.log("File created Succesfully" + JSON.stringify(success));
this.fileOpener.open(this.file.dataDirectory + fileName, 'application/pdf')
.then(() => console.log('File is opened'))
.catch(e => console.log('Error opening file', e));
})
.catch((error)=> {
this.loading.dismiss();
console.log("Cannot Create File " +JSON.stringify(error));
});
});
})
.catch(function (error) {
this.loading.dismiss();
console.error('oops, something went wrong!', error);
});
}
Test Run The Ionic 4 and Angular 6 Application on Device
Because we are using the Native plugin of Ionic 4 and Cordova, we should run on Device of Simulator to make the plugin working. Type this command to add Cordova platform.
ionic cordova platform add ios
ionic cordova platform add android
To run directly on iOS or Android Simulator or Device, type this commands.
ionic cordova run ios
ionic cordova run android
In my case, I've got this error when running on the Android device.
Failed to execute aapt
com.android.ide.common.process.ProcessException: Failed to execute aapt
at com.android.builder.core.AndroidBuilder.processResources(AndroidBuilder.java:796)
at com.android.build.gradle.tasks.ProcessAndroidResources.invokeAaptForSplit(ProcessAndroidResources.java:551)
at com.android.build.gradle.tasks.ProcessAndroidResources.doFullTaskAction(ProcessAndroidResources.java:285)
at com.android.build.gradle.internal.tasks.IncrementalTask.taskAction(IncrementalTask.java:109)
at sun.reflect.GeneratedMethodAccessor180.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$IncrementalTaskAction.doExecute(DefaultTaskClassInfoStore.java:173)
at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$StandardTaskAction.execute(DefaultTaskClassInfoStore.java:134)
at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$StandardTaskAction.execute(DefaultTaskClassInfoStore.java:121)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$1.run(ExecuteActionsTaskExecuter.java:122)
at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:336)
at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:328)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:197)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:107)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:111)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:92)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:70)
at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:63)
at org.gradle.api.internal.tasks.execution.ResolveTaskOutputCachingStateExecuter.execute(ResolveTaskOutputCachingStateExecuter.java:54)
at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:88)
at org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter.execute(ResolveTaskArtifactStateTaskExecuter.java:52)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:54)
at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:34)
at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker$1.run(DefaultTaskGraphExecuter.java:248)
at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:336)
at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:328)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:197)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:107)
at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:241)
at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:230)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker.processTask(DefaultTaskPlanExecutor.java:124)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker.access$200(DefaultTaskPlanExecutor.java:80)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:105)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:99)
at org.gradle.execution.taskgraph.DefaultTaskExecutionPlan.execute(DefaultTaskExecutionPlan.java:625)
at org.gradle.execution.taskgraph.DefaultTaskExecutionPlan.executeWithTask(DefaultTaskExecutionPlan.java:580)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker.run(DefaultTaskPlanExecutor.java:99)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:503)
at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:482)
at com.google.common.util.concurrent.AbstractFuture$TrustedFuture.get(AbstractFuture.java:79)
at com.android.builder.core.AndroidBuilder.processResources(AndroidBuilder.java:794)
... 47 more
Caused by: java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:503)
at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:462)
at com.google.common.util.concurrent.AbstractFuture$TrustedFuture.get(AbstractFuture.java:79)
at com.android.builder.internal.aapt.v2.QueueableAapt2.lambda$makeValidatedPackage$1(QueueableAapt2.java:179)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
... 1 more
Caused by: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
at com.android.builder.png.AaptProcess$NotifierProcessOutput.handleOutput(AaptProcess.java:463)
at com.android.builder.png.AaptProcess$NotifierProcessOutput.err(AaptProcess.java:415)
at com.android.builder.png.AaptProcess$ProcessOutputFacade.err(AaptProcess.java:332)
at com.android.utils.GrabProcessOutput$1.run(GrabProcessOutput.java:104)
FAILURE: Build failed with an exception.
To fix it, open and edit `platform/android/project.properties` then change this lines.
cordova.system.library.1=com.android.support:support-v4:+
cordova.system.library.2=com.android.support:support-annotations:27+
To this lines.
cordova.system.library.1=com.android.support:support-v4:27.1.0
cordova.system.library.2=com.android.support:support-annotations:27.1.0
You should see this page when it's running successfully.
That its, the Ionic 4, Angular 6 and Cordova: Export and View PDF File using `dom-to-image` and JSDF Javascript library. You can find the working source code in our GitHub.
We know that building beautifully designed Ionic apps from scratch can be frustrating and very time-consuming. Check Ionic 4 - Full Starter App and save development and design time. Android, iOS, and PWA, 100+ Screens and Components, the most complete and advance Ionic Template.
That just the basic. If you need more deep learning about Ionic, Angular, and Typescript, you can take the following cheap course:
- IONIC 4 Design Hybrid Mobile Applications IOS & Android
- Wordpress Rest API and Ionic 4 (Angular) App With Auth
- Mobile App from Development to Deployment - IONIC 4
- Ionic 4 Crash Course with Heartstone API & Angular
- Ionic 4 Mega Course: Build 10 Real World Apps
Thanks!