search
Angular

Angular 21 jsPDF Example to Edit & Modify PDF Files in Browser

Learn how to integrate jsPDF with Angular 21 to create, edit, and modify PDF documents directly in the browser using standalone components and signals.

person By Gautam Sharma
calendar_today December 30, 2024
schedule 15 min read
Angular PDF jsPDF TypeScript

Comprehensive guide to using jsPDF in Angular 21 applications with standalone components, signals, and modern TypeScript features for PDF generation and manipulation.

Installation

Install jsPDF and its types in your Angular project:

npm install jspdf
npm install --save-dev @types/jspdf

For HTML to PDF conversion, also install html2canvas:

npm install html2canvas

Basic Standalone Component

Create a simple PDF generator using Angular 21 standalone components:

// pdf-generator.component.ts
import { Component } from '@angular/core';
import jsPDF from 'jspdf';

@Component({
  selector: 'app-pdf-generator',
  standalone: true,
  template: `
    <div class="container">
      <h2>PDF Generator</h2>
      <button (click)="generatePDF()" class="btn">
        Download PDF
      </button>
    </div>
  `,
  styles: [`
    .container {
      padding: 20px;
    }
    .btn {
      background: #dd0031;
      color: white;
      padding: 12px 24px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 16px;
    }
    .btn:hover {
      background: #c3002f;
    }
  `]
})
export class PdfGeneratorComponent {
  generatePDF(): void {
    const doc = new jsPDF();
    doc.text('Hello from Angular 21!', 20, 20);
    doc.save('document.pdf');
  }
}

Using Angular Signals

Leverage Angular 21’s signals for reactive PDF generation:

import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import jsPDF from 'jspdf';

@Component({
  selector: 'app-pdf-form',
  standalone: true,
  imports: [FormsModule],
  template: `
    <div class="pdf-form">
      <h2>Create Your PDF</h2>

      <input
        [(ngModel)]="title"
        placeholder="Enter title"
        class="input"
      />

      <textarea
        [(ngModel)]="content"
        placeholder="Enter content"
        rows="5"
        class="textarea"
      ></textarea>

      <button (click)="createPDF()" class="btn">
        Create PDF
      </button>
    </div>
  `,
  styles: [`
    .pdf-form {
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
    }
    .input, .textarea {
      width: 100%;
      padding: 10px;
      margin: 10px 0;
      border: 1px solid #ddd;
      border-radius: 4px;
      font-family: inherit;
    }
    .btn {
      background: #dd0031;
      color: white;
      padding: 12px 24px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      width: 100%;
    }
  `]
})
export class PdfFormComponent {
  title = signal('');
  content = signal('');

  createPDF(): void {
    const doc = new jsPDF();

    doc.setFontSize(18);
    doc.text(this.title() || 'Untitled', 20, 20);

    doc.setFontSize(12);
    const lines = doc.splitTextToSize(this.content(), 170);
    doc.text(lines, 20, 35);

    doc.save(`${this.title() || 'document'}.pdf`);
  }
}

PDF Service with Dependency Injection

Create a reusable service for PDF operations:

// pdf.service.ts
import { Injectable } from '@angular/core';
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';

export interface PdfOptions {
  title: string;
  content: string;
  filename?: string;
}

@Injectable({
  providedIn: 'root'
})
export class PdfService {
  createBasicPDF(options: PdfOptions): jsPDF {
    const doc = new jsPDF();

    doc.setFontSize(18);
    doc.text(options.title, 20, 20);

    doc.setFontSize(12);
    const lines = doc.splitTextToSize(options.content, 170);
    doc.text(lines, 20, 35);

    return doc;
  }

  downloadPDF(doc: jsPDF, filename: string = 'document.pdf'): void {
    doc.save(filename);
  }

  previewPDF(doc: jsPDF): void {
    const blob = doc.output('blob');
    const url = URL.createObjectURL(blob);
    window.open(url, '_blank');
  }

  async elementToPDF(element: HTMLElement, filename: string = 'export.pdf'): Promise<void> {
    const canvas = await html2canvas(element);
    const imgData = canvas.toDataURL('image/png');

    const pdf = new jsPDF();
    const imgWidth = 190;
    const imgHeight = (canvas.height * imgWidth) / canvas.width;

    pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight);
    pdf.save(filename);
  }

  createStyledPDF(): jsPDF {
    const doc = new jsPDF();

    // Title
    doc.setFontSize(24);
    doc.setFont(undefined, 'bold');
    doc.text('Professional Document', 105, 20, { align: 'center' });

    // Subtitle
    doc.setFontSize(14);
    doc.setFont(undefined, 'italic');
    doc.setTextColor(100, 100, 100);
    doc.text('Generated with Angular 21', 105, 30, { align: 'center' });

    // Reset to normal
    doc.setTextColor(0, 0, 0);
    doc.setFont(undefined, 'normal');
    doc.setFontSize(12);

    // Content
    let y = 50;
    const content = [
      'This PDF demonstrates:',
      '',
      '• Multiple font sizes and styles',
      '• Text alignment and colors',
      '• Shapes and graphics',
      '• Professional formatting'
    ];

    content.forEach(line => {
      doc.text(line, 20, y);
      y += 8;
    });

    // Colored box
    y += 10;
    doc.setFillColor(221, 0, 49);
    doc.rect(20, y, 170, 20, 'F');
    doc.setTextColor(255, 255, 255);
    doc.setFontSize(13);
    doc.text('Angular + jsPDF', 105, y + 12, { align: 'center' });

    return doc;
  }
}

Use the service in a component:

import { Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { PdfService } from './pdf.service';

@Component({
  selector: 'app-pdf-creator',
  standalone: true,
  imports: [FormsModule],
  template: `
    <div class="container">
      <input [(ngModel)]="title" placeholder="Title" class="input" />
      <textarea [(ngModel)]="content" placeholder="Content" class="textarea"></textarea>

      <div class="button-group">
        <button (click)="download()" class="btn btn-primary">
          Download
        </button>
        <button (click)="preview()" class="btn btn-secondary">
          Preview
        </button>
      </div>
    </div>
  `,
  styles: [`
    .container {
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
    }
    .input, .textarea {
      width: 100%;
      padding: 10px;
      margin: 10px 0;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
    .button-group {
      display: flex;
      gap: 10px;
    }
    .btn {
      flex: 1;
      padding: 12px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    .btn-primary {
      background: #dd0031;
      color: white;
    }
    .btn-secondary {
      background: #555;
      color: white;
    }
  `]
})
export class PdfCreatorComponent {
  private pdfService = inject(PdfService);

  title = '';
  content = '';

  download(): void {
    const doc = this.pdfService.createBasicPDF({
      title: this.title,
      content: this.content
    });
    this.pdfService.downloadPDF(doc, `${this.title || 'document'}.pdf`);
  }

  preview(): void {
    const doc = this.pdfService.createBasicPDF({
      title: this.title,
      content: this.content
    });
    this.pdfService.previewPDF(doc);
  }
}

Invoice Generator Component

Complete invoice generator with Angular forms:

import { Component, signal } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { CommonModule } from '@angular/common';
import jsPDF from 'jspdf';

interface Invoice {
  clientName: string;
  email: string;
  invoiceNumber: string;
  amount: number;
  date: string;
  description: string;
}

@Component({
  selector: 'app-invoice-generator',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule],
  template: `
    <div class="invoice-container">
      <h2>Invoice Generator</h2>

      <form [formGroup]="invoiceForm" (ngSubmit)="generateInvoice()">
        <div class="form-row">
          <div class="form-group">
            <label>Client Name *</label>
            <input formControlName="clientName" class="input" />
            @if (invoiceForm.get('clientName')?.invalid && invoiceForm.get('clientName')?.touched) {
              <span class="error">Required</span>
            }
          </div>

          <div class="form-group">
            <label>Email *</label>
            <input formControlName="email" type="email" class="input" />
            @if (invoiceForm.get('email')?.invalid && invoiceForm.get('email')?.touched) {
              <span class="error">Valid email required</span>
            }
          </div>
        </div>

        <div class="form-row">
          <div class="form-group">
            <label>Invoice Number *</label>
            <input formControlName="invoiceNumber" class="input" />
          </div>

          <div class="form-group">
            <label>Date *</label>
            <input formControlName="date" type="date" class="input" />
          </div>
        </div>

        <div class="form-group">
          <label>Description *</label>
          <textarea formControlName="description" class="textarea" rows="3"></textarea>
        </div>

        <div class="form-group">
          <label>Amount *</label>
          <input formControlName="amount" type="number" step="0.01" class="input" />
        </div>

        <button type="submit" [disabled]="invoiceForm.invalid" class="btn">
          Generate Invoice PDF
        </button>
      </form>

      @if (generatedCount() > 0) {
        <div class="success-message">
          ✓ Generated {{ generatedCount() }} invoice(s)
        </div>
      }
    </div>
  `,
  styles: [`
    .invoice-container {
      max-width: 800px;
      margin: 0 auto;
      padding: 20px;
    }
    .form-row {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 15px;
    }
    .form-group {
      margin-bottom: 15px;
    }
    .form-group label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
      color: #333;
    }
    .input, .textarea {
      width: 100%;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 4px;
      font-family: inherit;
    }
    .error {
      color: #dd0031;
      font-size: 12px;
    }
    .btn {
      width: 100%;
      background: #dd0031;
      color: white;
      padding: 14px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 16px;
      font-weight: bold;
    }
    .btn:disabled {
      background: #ccc;
      cursor: not-allowed;
    }
    .success-message {
      margin-top: 20px;
      padding: 15px;
      background: #d4edda;
      color: #155724;
      border-radius: 4px;
      text-align: center;
    }
  `]
})
export class InvoiceGeneratorComponent {
  invoiceForm: FormGroup;
  generatedCount = signal(0);

  constructor(private fb: FormBuilder) {
    this.invoiceForm = this.fb.group({
      clientName: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]],
      invoiceNumber: ['', Validators.required],
      date: ['', Validators.required],
      description: ['', Validators.required],
      amount: ['', [Validators.required, Validators.min(0)]]
    });
  }

  generateInvoice(): void {
    if (this.invoiceForm.invalid) return;

    const invoice: Invoice = this.invoiceForm.value;
    const doc = new jsPDF();
    let y = 20;

    // Company header
    doc.setFontSize(22);
    doc.setFont(undefined, 'bold');
    doc.setTextColor(221, 0, 49);
    doc.text('YOUR COMPANY', 20, y);

    doc.setFontSize(10);
    doc.setTextColor(0, 0, 0);
    doc.setFont(undefined, 'normal');
    y += 10;
    doc.text('123 Business Street, City, State 12345', 20, y);
    y += 5;
    doc.text('Phone: (555) 123-4567', 20, y);
    y += 5;
    doc.text('contact@company.com', 20, y);

    // Invoice title
    y += 15;
    doc.setFontSize(24);
    doc.setFont(undefined, 'bold');
    doc.text('INVOICE', 20, y);

    // Invoice details
    y += 12;
    doc.setFontSize(11);
    doc.setFont(undefined, 'normal');
    doc.text(`Invoice #: ${invoice.invoiceNumber}`, 20, y);
    doc.text(`Date: ${invoice.date}`, 150, y);

    // Client info
    y += 15;
    doc.setFont(undefined, 'bold');
    doc.text('BILL TO:', 20, y);
    y += 6;
    doc.setFont(undefined, 'normal');
    doc.text(invoice.clientName, 20, y);
    y += 5;
    doc.text(invoice.email, 20, y);

    // Description table header
    y += 20;
    doc.setFillColor(240, 240, 240);
    doc.rect(20, y - 5, 170, 10, 'F');
    doc.setFont(undefined, 'bold');
    doc.text('Description', 22, y);
    doc.text('Amount', 165, y);

    // Description and amount
    y += 10;
    doc.setFont(undefined, 'normal');
    const descLines = doc.splitTextToSize(invoice.description, 120);
    doc.text(descLines, 22, y);
    doc.text(`$${invoice.amount.toFixed(2)}`, 165, y);

    // Total section
    y += Math.max(descLines.length * 5, 10) + 10;
    doc.line(115, y, 190, y);
    y += 8;
    doc.setFont(undefined, 'bold');
    doc.setFontSize(14);
    doc.text('TOTAL:', 130, y);
    doc.setFontSize(16);
    doc.setTextColor(221, 0, 49);
    doc.text(`$${invoice.amount.toFixed(2)}`, 165, y);

    // Footer
    const pageHeight = doc.internal.pageSize.height;
    doc.setFontSize(9);
    doc.setTextColor(100, 100, 100);
    doc.setFont(undefined, 'italic');
    doc.text('Thank you for your business!', 105, pageHeight - 30, { align: 'center' });
    doc.text('Payment is due within 30 days', 105, pageHeight - 25, { align: 'center' });

    doc.save(`invoice-${invoice.invoiceNumber}.pdf`);
    this.generatedCount.update(count => count + 1);
  }
}

Export Component to PDF

Capture and export Angular components as PDF:

import { Component, ElementRef, ViewChild, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PdfService } from './pdf.service';

@Component({
  selector: 'app-export-component',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="container">
      <div #content class="content-to-export">
        <h1>{{ title }}</h1>
        <p>{{ description }}</p>

        <ul>
          @for (item of items; track item) {
            <li>{{ item }}</li>
          }
        </ul>

        <div class="statistics">
          <div class="stat-card">
            <h3>Total Users</h3>
            <p class="stat-number">1,234</p>
          </div>
          <div class="stat-card">
            <h3>Active Sessions</h3>
            <p class="stat-number">567</p>
          </div>
        </div>
      </div>

      <button (click)="exportToPDF()" class="btn">
        Export to PDF
      </button>
    </div>
  `,
  styles: [`
    .container {
      max-width: 800px;
      margin: 0 auto;
      padding: 20px;
    }
    .content-to-export {
      padding: 30px;
      background: white;
      border: 1px solid #ddd;
      margin-bottom: 20px;
    }
    .statistics {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 20px;
      margin-top: 20px;
    }
    .stat-card {
      padding: 20px;
      background: #f5f5f5;
      border-radius: 8px;
      text-align: center;
    }
    .stat-number {
      font-size: 32px;
      font-weight: bold;
      color: #dd0031;
    }
    .btn {
      width: 100%;
      background: #dd0031;
      color: white;
      padding: 12px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
  `]
})
export class ExportComponentComponent {
  @ViewChild('content') content!: ElementRef<HTMLElement>;
  private pdfService = inject(PdfService);

  title = 'Export Report';
  description = 'This content will be exported to PDF';
  items = ['Feature 1', 'Feature 2', 'Feature 3'];

  async exportToPDF(): Promise<void> {
    await this.pdfService.elementToPDF(
      this.content.nativeElement,
      'export-report.pdf'
    );
  }
}

Multi-Page Reports

Generate complex multi-page reports:

import { Component } from '@angular/core';
import jsPDF from 'jspdf';

interface ReportData {
  title: string;
  sections: Array<{
    heading: string;
    content: string[];
  }>;
}

@Component({
  selector: 'app-report-generator',
  standalone: true,
  template: `
    <div class="container">
      <button (click)="generateReport()" class="btn">
        Generate Multi-Page Report
      </button>
    </div>
  `,
  styles: [`
    .container {
      padding: 20px;
      text-align: center;
    }
    .btn {
      background: #dd0031;
      color: white;
      padding: 14px 28px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 16px;
    }
  `]
})
export class ReportGeneratorComponent {
  generateReport(): void {
    const reportData: ReportData = {
      title: 'Annual Report 2024',
      sections: [
        {
          heading: 'Executive Summary',
          content: [
            'This report summarizes the key findings and achievements.',
            'Our organization has shown significant growth this year.',
            'Key metrics indicate positive trends across all departments.'
          ]
        },
        {
          heading: 'Financial Overview',
          content: [
            'Revenue increased by 25% compared to last year.',
            'Operating costs were maintained within budget.',
            'Profit margins improved by 15%.'
          ]
        },
        {
          heading: 'Future Outlook',
          content: [
            'We expect continued growth in the coming year.',
            'New initiatives are planned for market expansion.',
            'Investment in technology will drive efficiency.'
          ]
        }
      ]
    };

    const doc = new jsPDF();
    let currentPage = 1;

    // Cover page
    this.addCoverPage(doc, reportData.title, currentPage);

    // Content pages
    reportData.sections.forEach((section, index) => {
      doc.addPage();
      currentPage++;
      this.addContentPage(doc, section, currentPage);
    });

    doc.save('annual-report.pdf');
  }

  private addCoverPage(doc: jsPDF, title: string, pageNumber: number): void {
    const pageWidth = doc.internal.pageSize.width;
    const pageHeight = doc.internal.pageSize.height;

    // Background
    doc.setFillColor(221, 0, 49);
    doc.rect(0, 0, pageWidth, pageHeight / 2, 'F');

    // Title
    doc.setTextColor(255, 255, 255);
    doc.setFontSize(32);
    doc.setFont(undefined, 'bold');
    doc.text(title, pageWidth / 2, pageHeight / 4, { align: 'center' });

    // Subtitle
    doc.setFontSize(16);
    doc.setFont(undefined, 'normal');
    doc.text('Generated with Angular 21', pageWidth / 2, pageHeight / 3, { align: 'center' });

    // Date
    doc.setTextColor(0, 0, 0);
    doc.setFontSize(12);
    const date = new Date().toLocaleDateString();
    doc.text(`Generated: ${date}`, pageWidth / 2, pageHeight - 30, { align: 'center' });

    this.addPageNumber(doc, pageNumber);
  }

  private addContentPage(
    doc: jsPDF,
    section: { heading: string; content: string[] },
    pageNumber: number
  ): void {
    let y = 30;

    // Section heading
    doc.setFontSize(18);
    doc.setFont(undefined, 'bold');
    doc.setTextColor(221, 0, 49);
    doc.text(section.heading, 20, y);

    // Underline
    y += 2;
    doc.setDrawColor(221, 0, 49);
    doc.line(20, y, 190, y);

    // Content
    y += 15;
    doc.setTextColor(0, 0, 0);
    doc.setFontSize(12);
    doc.setFont(undefined, 'normal');

    section.content.forEach(paragraph => {
      const lines = doc.splitTextToSize(paragraph, 170);
      doc.text(lines, 20, y);
      y += lines.length * 7 + 5;
    });

    this.addPageNumber(doc, pageNumber);
  }

  private addPageNumber(doc: jsPDF, pageNumber: number): void {
    const pageHeight = doc.internal.pageSize.height;
    doc.setFontSize(10);
    doc.setTextColor(150, 150, 150);
    doc.text(`Page ${pageNumber}`, 105, pageHeight - 10, { align: 'center' });
  }
}

Table Generator

Create PDF documents with tables:

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import jsPDF from 'jspdf';

interface TableRow {
  id: number;
  name: string;
  email: string;
  status: string;
}

@Component({
  selector: 'app-table-pdf',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="container">
      <h2>User Data Table</h2>

      <table class="data-table">
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
            <th>Status</th>
          </tr>
        </thead>
        <tbody>
          @for (row of tableData; track row.id) {
            <tr>
              <td>{{ row.id }}</td>
              <td>{{ row.name }}</td>
              <td>{{ row.email }}</td>
              <td>{{ row.status }}</td>
            </tr>
          }
        </tbody>
      </table>

      <button (click)="exportTableToPDF()" class="btn">
        Export Table to PDF
      </button>
    </div>
  `,
  styles: [`
    .container {
      max-width: 900px;
      margin: 0 auto;
      padding: 20px;
    }
    .data-table {
      width: 100%;
      border-collapse: collapse;
      margin: 20px 0;
    }
    .data-table th,
    .data-table td {
      padding: 12px;
      text-align: left;
      border-bottom: 1px solid #ddd;
    }
    .data-table th {
      background: #dd0031;
      color: white;
      font-weight: bold;
    }
    .data-table tr:hover {
      background: #f5f5f5;
    }
    .btn {
      background: #dd0031;
      color: white;
      padding: 12px 24px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
  `]
})
export class TablePdfComponent {
  tableData: TableRow[] = [
    { id: 1, name: 'John Doe', email: 'john@example.com', status: 'Active' },
    { id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'Active' },
    { id: 3, name: 'Bob Johnson', email: 'bob@example.com', status: 'Inactive' },
    { id: 4, name: 'Alice Brown', email: 'alice@example.com', status: 'Active' }
  ];

  exportTableToPDF(): void {
    const doc = new jsPDF();

    // Title
    doc.setFontSize(18);
    doc.setFont(undefined, 'bold');
    doc.text('User Data Report', 105, 20, { align: 'center' });

    // Table headers
    let y = 40;
    const headers = ['ID', 'Name', 'Email', 'Status'];
    const colWidths = [20, 50, 70, 30];
    let x = 20;

    doc.setFillColor(221, 0, 49);
    doc.rect(20, y - 7, 170, 10, 'F');

    doc.setTextColor(255, 255, 255);
    doc.setFontSize(12);
    doc.setFont(undefined, 'bold');

    headers.forEach((header, i) => {
      doc.text(header, x, y);
      x += colWidths[i];
    });

    // Table rows
    y += 10;
    doc.setTextColor(0, 0, 0);
    doc.setFont(undefined, 'normal');
    doc.setFontSize(11);

    this.tableData.forEach((row, index) => {
      x = 20;

      // Alternating row colors
      if (index % 2 === 0) {
        doc.setFillColor(245, 245, 245);
        doc.rect(20, y - 6, 170, 8, 'F');
      }

      doc.text(row.id.toString(), x, y);
      x += colWidths[0];

      doc.text(row.name, x, y);
      x += colWidths[1];

      doc.text(row.email, x, y);
      x += colWidths[2];

      doc.text(row.status, x, y);

      y += 10;
    });

    doc.save('user-data.pdf');
  }
}

Adding Images

Handle image uploads and add them to PDFs:

import { Component, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import jsPDF from 'jspdf';

@Component({
  selector: 'app-image-pdf',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="container">
      <h2>Add Image to PDF</h2>

      <input
        type="file"
        (change)="handleFileUpload($event)"
        accept="image/*"
        class="file-input"
      />

      @if (imagePreview()) {
        <div class="preview">
          <img [src]="imagePreview()" alt="Preview" />
        </div>
      }

      <button
        (click)="generatePDFWithImage()"
        [disabled]="!imageData()"
        class="btn"
      >
        Generate PDF with Image
      </button>
    </div>
  `,
  styles: [`
    .container {
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
    }
    .file-input {
      margin: 20px 0;
    }
    .preview {
      margin: 20px 0;
      text-align: center;
    }
    .preview img {
      max-width: 100%;
      max-height: 300px;
      border: 2px solid #ddd;
      border-radius: 8px;
    }
    .btn {
      width: 100%;
      background: #dd0031;
      color: white;
      padding: 12px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    .btn:disabled {
      background: #ccc;
      cursor: not-allowed;
    }
  `]
})
export class ImagePdfComponent {
  imageData = signal<string | null>(null);
  imagePreview = signal<string | null>(null);

  handleFileUpload(event: Event): void {
    const input = event.target as HTMLInputElement;
    const file = input.files?.[0];

    if (file) {
      const reader = new FileReader();

      reader.onload = (e) => {
        const result = e.target?.result as string;
        this.imageData.set(result);
        this.imagePreview.set(result);
      };

      reader.readAsDataURL(file);
    }
  }

  generatePDFWithImage(): void {
    const data = this.imageData();
    if (!data) return;

    const doc = new jsPDF();

    doc.setFontSize(18);
    doc.setFont(undefined, 'bold');
    doc.text('Document with Image', 105, 20, { align: 'center' });

    doc.setFontSize(12);
    doc.setFont(undefined, 'normal');
    doc.text('Image embedded in PDF:', 20, 35);

    doc.addImage(data, 'PNG', 15, 45, 180, 120);

    doc.save('with-image.pdf');
  }
}

Best Practices for Angular 21

  1. Use Standalone Components: Leverage Angular 21’s standalone architecture
  2. Signals for Reactivity: Use signals for reactive state management
  3. Inject Function: Use inject() for cleaner dependency injection
  4. Control Flow Syntax: Use @if, @for for better template syntax
  5. Type Safety: Leverage TypeScript for type-safe PDF operations
  6. Service Layer: Create reusable PDF services with providedIn: 'root'
  7. Async Operations: Use async/await for HTML to PDF conversions
  8. Error Handling: Implement proper error handling and user feedback
  9. Loading States: Show loading indicators during PDF generation
  10. Form Validation: Use Angular’s reactive forms for input validation

Common jsPDF Methods

  • new jsPDF() - Create new document
  • doc.text(text, x, y) - Add text
  • doc.setFontSize(size) - Set font size
  • doc.setFont(font, style) - Set font style
  • doc.setTextColor(r, g, b) - Set text color
  • doc.addPage() - Add new page
  • doc.addImage(data, format, x, y, w, h) - Add image
  • doc.rect(x, y, w, h, style) - Draw rectangle
  • doc.line(x1, y1, x2, y2) - Draw line
  • doc.save(filename) - Download PDF

Performance Tips

  1. Generate PDFs only on user action
  2. Show loading states for large documents
  3. Optimize images before adding to PDFs
  4. Use OnPush change detection strategy
  5. Implement lazy loading for PDF components
  6. Cache reusable PDF templates
  7. Use Web Workers for heavy PDF operations

Browser Compatibility

jsPDF works in all modern browsers with Angular 21:

  • Chrome 60+
  • Firefox 55+
  • Safari 11+
  • Edge 79+

You now have comprehensive knowledge to create professional PDF documents in Angular 21 applications using jsPDF!

Gautam Sharma

About Gautam Sharma

Full-stack developer and tech blogger sharing coding tutorials and best practices

Related Articles

Angular

Generate and Scan QR Codes in Angular

Quick guide to generating and scanning QR codes in Angular browser applications. Simple examples with ngx-qrcode and html5-qrcode.

December 31, 2024
React

How to integrate jsPDF Library in React to Edit PDF in Browser

Quick guide to using jsPDF in React applications for creating and editing PDF documents directly in the browser.

December 29, 2024
HTML

jsPDF Tutorial: Generate PDF in Browser Using HTML & JavaScript (Full Working Example)

Learn to create PDFs directly in the browser with jsPDF. Step-by-step guide with working examples for invoices, tickets, and styled documents.

December 31, 2024