ScreenshotNeo

BlogHTML to image & PDF

How to Convert HTML to PDF in AngularJS

Learn how to export a rendered AngularJS view to PDF with complete client-side code, print-ready CSS, troubleshooting, and a serverless alternative.

By the ScreenshotNeo team30 September 20266 min read

How to Convert HTML to PDF in AngularJS

Direct answer: AngularJS renders the HTML view, but it does not generate PDFs itself. To export a rendered view, wait until AngularJS has finished updating the DOM, select a stable element, and pass it to a browser PDF library such as html2pdf.js, which combines DOM rendering with jsPDF. For print-oriented documents, you can also use a dedicated print view and the browser’s Save as PDF flow.

AngularJS support officially ended in January 2022, so this approach is mainly useful for maintaining an existing application. Read the AngularJS project notice before choosing long-term dependencies.

1. Choose the right PDF approach

Approach Best fit Main limitations
Client-side DOM conversion Small or moderate documents generated from the current browser view Unsupported CSS, cross-origin images, iframes, and large documents can reduce fidelity
Browser print flow Print-specific layouts where users can choose Save as PDF Depends on browser print settings and user interaction
Server-side rendering Centralized generation, scheduled jobs, or consistent output across clients Requires infrastructure, asset handling, fonts, security controls, and renderer maintenance

2. Build a complete client-side AngularJS example

The example below exports only #pdf-content. Keeping the export area separate from navigation, buttons, and interactive controls produces more predictable output.

The export pipeline waits for AngularJS rendering before converting the selected DOM element.
The export pipeline waits for AngularJS rendering before converting the selected DOM element.

Install the libraries

npm install html2pdf.js

Use the API that matches the version installed in your application. The package documentation describes the DOM-node, A4, and output options used here: html2pdf.js documentation.

AngularJS markup

<div ng-app="reportApp" ng-controller="ReportController as report">
  <button type="button" ng-click="report.exportPdf()">
    Export PDF
  </button>

  <section id="pdf-content" class="pdf-document">
    <h1>{{ report.title }}</h1>
    <p>Generated for {{ report.customer }}</p>

    <article ng-repeat="item in report.items track by item.id" class="report-row">
      <h2>{{ item.name }}</h2>
      <p>{{ item.description }}</p>
    </article>
  </section>
</div>

Controller and export function

angular.module('reportApp', [])
  .controller('ReportController', ['$timeout', function ($timeout) {
    var vm = this;

    vm.title = 'Quarterly report';
    vm.customer = 'Example customer';
    vm.items = [
      { id: 1, name: 'Revenue', description: 'Revenue increased during the quarter.' },
      { id: 2, name: 'Operations', description: 'Operational targets were reviewed.' }
    ];

    vm.exportPdf = function () {
      var element = document.getElementById('pdf-content');
      if (!element) {
        throw new Error('The export element was not found.');
      }

      // Let AngularJS finish the current digest and let the browser paint.
      $timeout(function () {
        html2pdf().set({
          margin: [12, 12, 12, 12],
          filename: 'quarterly-report.pdf',
          image: { type: 'jpeg', quality: 0.95 },
          html2canvas: {
            scale: 2,
            useCORS: true,
            backgroundColor: '#ffffff'
          },
          jsPDF: {
            unit: 'mm',
            format: 'a4',
            orientation: 'portrait'
          },
          pagebreak: {
            mode: ['css', 'legacy']
          }
        }).from(element).save();
      }, 0);
    };
  }]);
.pdf-document {
  width: 180mm;
  margin: 0 auto;
  color: #111;
  background: #fff;
  font-family: Arial, sans-serif;
}

.report-row {
  break-inside: avoid;
  page-break-inside: avoid;
  margin-bottom: 12mm;
}

@media print {
  .no-print,
  button {
    display: none !important;
  }
}

.page-break {
  break-before: page;
  page-break-before: always;
}

3. Export only after AngularJS has rendered

Calling the exporter before an ng-repeat, image, or asynchronous request has completed can produce an incomplete PDF. Use one of these patterns:

  • Call the export function from a button after the data request has resolved.
  • Use $timeout(..., 0) or $evalAsync to wait for the digest cycle.
  • Wait for images with a small promise before starting conversion.
  • For content loaded after scrolling, ensure lazy-loaded sections are expanded first.
function waitForImages(root) {
  var images = Array.prototype.slice.call(root.querySelectorAll('img'));
  return Promise.all(images.map(function (img) {
    if (img.complete) return Promise.resolve();
    return new Promise(function (resolve) {
      img.addEventListener('load', resolve, { once: true });
      img.addEventListener('error', resolve, { once: true });
    });
  }));
}

vm.exportPdf = function () {
  var element = document.getElementById('pdf-content');
  $timeout(function () {
    waitForImages(element).then(function () {
      return html2pdf().set({ filename: 'report.pdf' }).from(element).save();
    });
  }, 0);
};

4. Dynamic HTML and ng-bind-html

If the export view uses ng-bind-html, AngularJS documentation says to include ngSanitize as a module dependency so AngularJS can use $sanitize. Sanitizing content is a security requirement; exporting or displaying arbitrary HTML does not make it safe.

<script src="angular.js"></script>
<script src="angular-sanitize.js"></script>
<script>
angular.module('reportApp', ['ngSanitize'])
  .controller('ReportController', ['$sce', function ($sce) {
    this.html = '<p>Sanitized report content</p>';
  }]);
</script>

See the AngularJS $sanitize documentation.

5. Important rendering limits

  • DOM reconstruction is not a literal screenshot. DOM-to-canvas tools rebuild the page from supported DOM and CSS properties. Some effects, layout features, and fonts may differ from the browser view. See the html2canvas documentation.
  • Cross-origin images can fail. Images need suitable CORS headers and a compatible crossorigin setup. Otherwise the canvas may be tainted or the image may be omitted.
  • Cross-origin iframes cannot be read. Export the iframe’s content from its own origin or replace it with data available to the parent page.
  • Large documents consume memory. Lower the canvas scale, split the document, or generate it on a server when browser memory becomes a problem.
  • Fonts must be loaded first. Wait for web fonts before conversion if typography matters.

6. Useful configuration options

Option Purpose
margin Sets page margins in the selected jsPDF unit.
filename Sets the downloaded file name.
image.type and quality Controls the raster image format and compression.
html2canvas.scale Higher values can improve sharpness but increase memory use.
html2canvas.useCORS Attempts CORS-enabled image loading; the server must still send compatible headers.
jsPDF.format Chooses paper size such as A4.
jsPDF.orientation Uses portrait or landscape.
pagebreak.mode Uses CSS and legacy page-break rules.

7. Troubleshooting

Symptom Likely cause Fix
Blank or partial PDF Export ran before AngularJS finished rendering Start after the data promise resolves, then defer with $timeout; wait for images and fonts.
Images missing Cross-origin restrictions or failed image requests Serve images with CORS headers, use compatible crossorigin attributes, or proxy approved assets.
Iframe content missing Cross-origin iframe isolation Render the iframe content separately or provide same-origin data.
CSS looks different DOM-to-canvas supports only parts of CSS Simplify the export CSS, avoid unsupported effects, and validate representative pages.
Text is blurry Low canvas scale or rasterized output Increase html2canvas.scale carefully, or use a renderer that preserves selectable text.
Rows split awkwardly Page-break rules are missing or unsupported Add break-inside: avoid, insert explicit page-break elements, and test long rows.
ng-bind-html does not render ngSanitize is not loaded or declared Load angular-sanitize.js and add 'ngSanitize' to the module.
Browser freezes Very large DOM or high raster scale Export smaller sections, reduce scale, remove hidden content, or move generation server-side.

8. Reliability, performance, and cost

  • Use a dedicated export template with fixed widths, explicit colors, and predictable spacing.
  • Keep export data deterministic. Capture after all API requests, images, and fonts are ready.
  • Test short, long, multilingual, image-heavy, and empty states.
  • Measure generation time and browser memory for your largest expected document.
  • Client-side conversion avoids a PDF server but uses the reader’s CPU and memory.
  • For regulated or sensitive data, review where the browser and any server-side renderer process the document.

9. Or skip the browser setup

ScreenshotNeo provides a website capture API and MCP server. It can capture a URL as PNG, JPEG, WebP, or PDF, with options for full-page output, custom CSS and JavaScript, waiting for selectors or network idle, headers, cookies, authentication, device presets, and PDF paper settings. Cookie banners, newsletter popups, and chat widgets are removed before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed.

A capture service can remove common consent banners and overlays before generating the document.
A capture service can remove common consent banners and overlays before generating the document.

See the ScreenshotNeo API documentation for PDF parameters and the complete option list.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/report -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com/report"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/report' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Every response identifies the result with X-Page-Verdict and X-Billed headers. ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.

10. FAQ

Does AngularJS convert HTML to PDF by itself?

No. AngularJS renders the view; a browser library, print flow, or server-side renderer creates the PDF.

Can I export a single AngularJS element?

Yes. Pass the specific DOM node, such as document.getElementById('pdf-content'), to the conversion library.

Will the generated PDF contain selectable text?

DOM-to-canvas workflows commonly rasterize the rendered content. If selectable text is required, evaluate a renderer designed to produce text-based PDFs.

Should a legacy AngularJS app keep using this approach?

It can be practical for maintenance, but AngularJS reached official end of life in January 2022. Include dependency and migration planning in your decision.