Ionic 4, Angular 6 and Cordova: Export and View PDF File

by Didin J. on Oct 04, 2018 Ionic 4, Angular 6 and Cordova: Export and View PDF File

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

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:


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:

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.

Ionic 4, Angular 6 and Cordova: Export and View PDF File - Ionic 4 Blank


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.

Ionic 4, Angular 6 and Cordova: Export and View PDF File - Invoice page
Ionic 4, Angular 6 and Cordova: Export and View PDF File - PDF result

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:

Thanks!