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 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


1. Ionic 4, Angular 6 and Cordova: Create a New 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 asking 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


2. Ionic 4, Angular 6 and Cordova: 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.

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.


3. Ionic 4, Angular 6 and Cordova: 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-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 this lines of SASS codes.

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;
        }
      }
    }
  }
}


4. Ionic 4, Angular 6 and Cordova: 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 this imports.

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 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 for export and preview 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);
  });
}


5. Ionic 4, Angular 6 and Cordova: Test Run The 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 Ion2FullApp ELITE - The Complete Ionic 3 Starter App and save development and design time.

That just the basic. If you need more deep learning about Ionic, Angular, and Typescript, you can find the following books:

Or take the following course:

Thanks!

The following resources might be useful for you:

Loading…