Reader Stacks

Converting HTML to PDF in Angular

Two genuinely different approaches exist — rendering the DOM to an image-based PDF entirely in the browser, or asking a server to generate a real, selectable-text PDF — and they aren't interchangeable.

"Convert this HTML to a PDF" in Angular splits into two genuinely different techniques with different trade-offs — a fully client-side approach that screenshots the rendered DOM, and a server-generated approach that produces a proper document. Which one fits depends entirely on what the resulting PDF actually needs to do.

1. Client-side: html2canvas + jsPDF

npm install html2canvas jspdf
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';

async function exportToPdf(elementId: string): Promise<void> {
  const element = document.getElementById(elementId);
  if (!element) return;

  const canvas = await html2canvas(element);
  const imageData = canvas.toDataURL('image/png');

  const pdf = new jsPDF({ orientation: 'portrait', unit: 'px', format: 'a4' });
  pdf.addImage(imageData, 'PNG', 0, 0, canvas.width, canvas.height);
  pdf.save('export.pdf');
}

html2canvas renders the target DOM element to a canvas (essentially a screenshot), and jsPDF embeds that image into a PDF file — this runs entirely in the browser with no backend involved, which is genuinely convenient, but the resulting PDF contains a picture of the text, not real, selectable text.

2. Why the client-side approach has real limitations

Because the output is an image, not real text: search doesn't work inside the PDF, text can't be selected or copied, screen readers can't read it, file size is often larger than an equivalent text-based PDF, and print quality can look noticeably worse than native PDF text, especially at higher zoom levels. For a receipt, a simple certificate, or anything primarily visual, this is a reasonable trade-off; for anything meant to be a real, searchable, accessible document, it isn't.

3. Server-side: generating a genuine PDF with real text

The more robust approach sends the data (not rendered HTML) to a backend, which generates a proper PDF using a dedicated library — in a Laravel backend, for example, barryvdh/laravel-dompdf or a headless-browser-based tool like Puppeteer/Playwright running server-side render real text-based PDFs from an HTML template.

// Angular side — just requesting the generated file
this.http.get('/api/invoices/42/pdf', { responseType: 'blob' }).subscribe((blob) => {
  const url = window.URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = url;
  link.download = 'invoice-42.pdf';
  link.click();
  window.URL.revokeObjectURL(url);
});

The Angular side of this pattern is simple regardless of which server-side PDF library generates the file — request the endpoint with responseType: 'blob', then trigger a download from the returned binary data.

4. Choosing between the two approaches

  • Client-side (html2canvas + jsPDF) — quick to set up, no backend changes needed, fine for a simple visual export where searchable/selectable text doesn't matter.
  • Server-side — more setup (a backend endpoint and PDF library), but produces a genuine, accessible, searchable document — the right choice for invoices, reports, contracts, or anything users are likely to search, copy from, or need read aloud by assistive technology.

5. A common mistake: exporting a live, interactive UI as-is

Directly screenshotting a page's actual UI (with buttons, hover states, scrollbars) usually produces an odd-looking PDF — a dedicated print-specific template or view, styled deliberately for the printed page (different layout, no interactive elements, appropriate margins), produces a meaningfully better result than capturing whatever's currently on screen.

Topics: APIs & Integrations