Angular2 - Creating PDF documents from HTML content with jspdf

For my current project, I am in need of generating a PDF of the page that the user is currently viewing. To accomplish this task, I have decided to utilize jspdf. Since I have HTML content that needs to be converted into a PDF format, I will make use of the addHTML() function provided by jspdf. There are several discussions on this topic, where some suggest:

You can either opt for html2canvas or rasterizehtml.

After considering both options, I have chosen to go with html2canvas. Below is a snippet of the code that I have implemented so far:

import { Injectable, ElementRef, ViewChild } from '@angular/core';
import * as jsPDF from 'jspdf';
import * as d3 from 'd3';
import * as html2canvas from 'html2canvas';

@Injectable ()
export class pdfGeneratorService {

  @ViewChild('to-pdf') element: ElementRef;

  GeneratePDF () {
    html2canvas(this.element.nativeElement, <Html2Canvas.Html2CanvasOptions>{
      onrendered: function(canvas: HTMLCanvasElement) {
        var pdf = new jsPDF('p','pt','a4');

        pdf.addHTML(canvas, function() {
          pdf.save('web.pdf');
        });
      }
    });
  }
}

However, upon invoking this function, I encounter an error message in the console:

EXCEPTION: Error in ./AppComponent class AppComponent - inline template:3:4 caused by: You need either https://github.com/niklasvh/html2canvas or https://github.com/cburgmer/rasterizeHTML.js

This leaves me puzzled as to why I am prompted to use html2canvas despite passing a canvas parameter. What could be the reason behind this requirement?

Answer №1

After some experimentation, I discovered a solution that worked well.

<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.js"></script>

I found that adding the above script to the index.html file did the trick (although it could potentially be included elsewhere in the project).

Next, I implemented the following JavaScript code:

const elementToPrint = document.getElementById('foo'); //The specific html element to convert into a pdf
const pdf = new jsPDF('p', 'pt', 'a4');
pdf.addHTML(elementToPrint, () => {
    doc.save('web.pdf');
});

This method no longer requires referencing html2canvas within the code.
You can now safely remove the following import statement:

import * as html2canvas from 'html2canvas';

Answer №2

If you prefer not to use cdn scripts and would rather take an (angular) approach, here is a method that worked for me in Angular 6:

By using this method, you will have better support and autocomplete in the editor, and it will allow you to avoid relying on cdn scripts (if you wish to avoid them, like I do).

I found the solution based on an excellent answer here, which was difficult for me to find initially. Therefore, I am sharing what was mentioned in that answer, as it helped me utilize jsPDF in Angular 6 (all credit goes to the original author of the answer).

You need to run these commands:

npm install jspdf --save

typings install dt~jspdf --global --save

npm install @types/jspdf --save

Add the following to angular-cli.json:

"scripts": [ "../node_modules/jspdf/dist/jspdf.min.js" ]

In your html file:

<button (click)="download()">Download</button>

In your component TypeScript file:

import { Component, OnInit, Inject } from '@angular/core';
import * as jsPDF from 'jspdf'
@Component({
  ...
  providers: [
    { provide: 'Window',  useValue: window }
  ]
})
export class GeneratePdfComponent implements OnInit {

  constructor(
    @Inject('Window') private window: Window,
    ) { }

  download() {
        var doc = new jsPDF();
        doc.text(20, 20, 'Hello world!');
        doc.text(20, 30, 'This is client-side Javascript, generating a PDF.');
        doc.addPage();
        doc.text(20, 20, 'Do you like that?');

        // Save the PDF
        doc.save('Test.pdf');
    }
}

Answer №3

If you're looking to convert an HTML div to a PDF, consider using the convenient tool html2pdf. With just a few lines of code, you can easily accomplish this task.

var item = document.getElementById('item-to-convert');
html2pdf(item);

Answer №4

Here's an example for you to try out:

CreatePDF () {
    html2canvas(this.element.nativeElement, <Html2Canvas.Html2CanvasOptions>{
      onrendered: function(canvas: HTMLCanvasElement) {
        var pdf = new jsPDF('p','pt','a4');    
        var img = canvas.toDataURL("image/png");
        pdf.addImage(img, 'PNG', 10, 10, 580, 300);
        pdf.save('output.pdf');
      }
    });
  }

Answer №5

Follow this method StackBlitz sample

import {jsPDF} from 'jspdf';

@ViewChild('content', {static: false}) content: ElementRef;


public createPDF() {
   const pdfDoc = new jsPDF();

   const specialElements = {
      '#editor': function (element, renderer) {
       return true;
       }
   };

   const contentElement = this.content.nativeElement;

   pdfDoc.fromHTML(contentElement.innerHTML, 15, 15, {
      width: 190,
     'elementHandlers': specialElements
   });

   pdfDoc.save('myDocument.pdf');
}

Answer №6

For those new to this query, make sure you check the version of jspdf you are using:

1.xx:

You will need to install html2canvas first

   npm install html2canvas

Then add it to the scripts section in angular.json

//.angular.json  
 "scripts": [ 
               // ensure correct path is used 
               "node_modules/html2canvas/dist/html2canvas.min.js"
            ]  
   // let data = this.htmlData.nativeElement; if you use it with @ViewChild('htmlData') 
    htmlData:ElementRef;
    let data = document.body;
    html2canvas(data).then(canvas => {
    // Some necessary setting options
    const imgWidth = 208;
    const pageHeight = 295;
    const imgHeight = canvas.height * imgWidth / canvas.width;
    const heightLeft = imgHeight;

    const contentDataURL = canvas.toDataURL('image/png')
    let pdf = new jspdf('p', 'mm', 'a4'); // A4 size page of PDF
    var position = 0;
    pdf.addImage(contentDataURL, 'PNG', 0, position, imgWidth, imgHeight)
    pdf.save('new-file.pdf'); // PDF generated
    });

2.xx:

There are notable changes between versions 1.xx and 2.xx:

Deprecated APIs have been removed. These include: addHTML, fromHTML, html2pdf, addSvg, addButton, addTextField, addChoiceField, cellInitialize, setFontStyle, setFontType, clip_fixed.

The previous code should be adjusted as follows:

 // let data = this.htmlData.nativeElement; if you use it with @ViewChild('htmlData') 
 htmlData:ElementRef;
 let data = document.body;
    let options : any = {
      orientation: 'p',
      unit: 'px',
      format: 'a0',
      };
    let doc = new jsPDF(options);
     doc.html(data.innerHTML, {
      callback: function (doc) {
            doc.save("angular-demo.pdf");
          },
      margin:15,
      x: 10,
      y: 10
    });
  }

demo

Note that in version 2.xx, there's no need to manually install html2canvas via npm or include the script in angular.json/index.html as it is now handled dynamically internally.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountering a Typescript issue while trying to access two distinct values dynamically from within a single object

Currently, I'm developing a component library and facing an issue with a TypeScript error: An Element implicitly has an 'any' type due to the expression of type 'levelTypes | semanticTypes' being unable to index type '{ level1 ...

Error in Typescript/React: Unable to access the property 'MaxEmailLength' as it is undefined

I am facing an unusual problem with TypeScript. I have two static classes that are mutually referencing each other and causing issues. Class ValidationHelper (single file) import { ValidationErrors } from '../dictionary/ValidationErrors'; ex ...

When attempting to Dockerize an Angular CLI app, nothing seemed to happen

I attempted to dockerize my Angular application and created a Dockerfile as follows: FROM teracy/angular-cli as angular-built WORKDIR /usr/src/app COPY package.json package.json RUN npm install COPY . . RUN ng build FROM nginx:alpine LABEL author="pleijan ...

How can one dynamically update a page in Angular when the path is changed?

I am facing a pagination issue in Angular. Here is my HTML code: <!-- A div element for pagination part at the bottom of the page --> <div style="margin-left: 50%; margin-top: 20px; margin-bottom: 20px"> <ul class="paginat ...

Creating a personalized theme for Material UI 5.0 using Typescript with React

Having some trouble customizing a theme in Material UI 5.0 with typescript. theme.ts import { createTheme } from '@mui/material'; declare module '@mui/material/styles' { interface Theme { custom: { buttonWi ...

The type definition file for '@types' is not present in Ionic's code base

After updating my Ionic 6 project to use Angular 3, everything works perfectly in debug mode. However, when I attempt to compile for production using 'ionic build --prod' or 'ionic cordova build android --prod', I encounter the followin ...

Saving a PDF file to iBooks using WKWebView and downloading a PDF file directly from a provided

Hi there, I'm currently working on a project as a Junior developer. I am using WKWebView in my app, and it includes links to open PDF files. While I can open these PDFs in Safari and then transfer them to iBooks, I would prefer to keep the process int ...

Extract a string value from a TypeScript enum

Here is a basic enum definition: export enum Type { TEST_ONE = "testing.one", TEST_TWO = "testing.two", BETA = "beta.one" } I am looking to run a function for each string value in the enum. For example: executeType(type: string) { console.lo ...

Incorporate a typescript library into your Angular application

Recently, I added a text editor called Jodit to my angular application and faced some challenges in integrating it smoothly. The steps I followed were: npm install --save jodit Inserted "node_modules/jodit/build/jodit.min.js" in angular.json's bui ...

The return type is not undefined but the switch covers all possibilities

I'm struggling to understand the issue with this simple example interface List { "A": false, "B": false, } // The function is missing a return statement and its return type does not include 'undefined'.ts(2366) / ...

Is there a way to stop the BS modal from appearing when I hit Enter after filling out a form?

Currently, I am implementing a form within an Angular framework. Every time I press the Enter key in any input field, it triggers the opening of a bsmodal dialog. ...

Exporting numerous modules from an NPM package

Currently, I am working on a large Node and Typescript project called Project A. This project consists of numerous modules that I intend to reuse in another project, Project B. In order to achieve this reusability, I have configured the tsconfig.json file ...

Circular dependency situation encountered in Angular 2 shared module

I'm currently working on a share module setup that is structured as follows: @NgModule({ exports: [ CommonModule, HttpModule, OneModule, TwoModule ] }) export class SharedModule { } The OneModule imports the SharedModule in order ...

Can my https.post function determine the specific API function to call?

Here is the code for my service file that interacts with mongoDB: import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { filter, isEmpty, map } from 'rxjs/operators'; import { ...

Field that only permits numerical input without triggering events for other characters

I've encountered some issues with the default behavior of the HTML number input and I'm looking to create a simple input that only allows numbers. To address this, I have developed a directive as shown below: import { Directive, ElementRef, Hos ...

Removing a nested data object in Angular with NGXS

I have successfully implemented the get and delete functions for posts in my Angular app. However, I am facing issues when trying to delete a comment within a post using NGXS. How can I access the comment inside the post in order to delete it? Here's ...

Express encounters difficulty in processing Chunked Post Data

I am currently retrieving data from a Campbell Scientific data logger. This data is being posted to an application that is coded in Typescript using Express and BodyParser. The request successfully reaches the app (as I'm able to debug it), however, t ...

Create a nested array of subcategories within an array object

Currently, I am working on integrating Django Rest and Angular. The JSON array received from the server includes category and subcategory values. My goal is to organize the data such that each category has its related subcategories stored as an array withi ...

Tips for setting values to the child component property in Angular 4

When I was using ngif, I encountered an issue with getting the element reference of the child component. After extensive searching, I discovered that in order to access the element, I needed to use view children instead of view child. While I am able to ge ...

Recording changes in SVG size in Angular 2

I am aiming to create an SVG canvas within an Angular 2 template that automatically scales with its parent element and triggers a redraw method when its size changes. While using the onresize property, I successfully receive events but encounter difficult ...