How to Export an HTML Table to PDF or Excel in AngularJS
Export AngularJS tables to XLSX or PDF with complete DOM and data-driven examples, options, troubleshooting, and a browser-free ScreenshotNeo workflow.
Short answer: For an HTML table already rendered by AngularJS, use SheetJS XLSX.utils.table_to_book and XLSX.writeFile for Excel. For PDF, use jsPDF-AutoTable with html pointing to the table. If the controller’s records are the authoritative source, build the export from that data instead of parsing the DOM.
Choose the right export source
| Requirement | Best source | Reason |
|---|---|---|
| Export exactly what the user sees | Rendered DOM table | Uses the current rows, columns and displayed values. |
| Export controller records | AngularJS data array | Gives explicit control over fields, order and value conversion. |
| Preserve spreadsheet types | Data-driven worksheet | You can normalize dates, numbers and nulls before writing. |
| Produce a printable report | PDF table | AutoTable can parse HTML or accept structured headings and rows. |
Do not assume a DOM export is a faithful copy of your database. Filtering, pagination, hidden cells, formatted dates and generated labels can all change what is exported.
Export a rendered AngularJS table to Excel
SheetJS documents an AngularJS workflow that locates the actual TABLE element, converts it to a workbook and downloads an XLSX file. See the AngularJS example and the HTML table API.
<div ng-app="reportApp" ng-controller="ReportController">
<table id="tbl" border="1">
<thead>
<tr>
<th>Customer</th>
<th>Orders</th>
<th>Revenue</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td>{{ row.customer }}</td>
<td>{{ row.orders }}</td>
<td>{{ row.revenue | currency }}</td>
</tr>
</tbody>
</table>
<button type="button" ng-click="exportExcel()">Download Excel</button>
</div>
angular.module('reportApp', [])
.controller('ReportController', ['$scope', function ($scope) {
$scope.rows = [
{ customer: 'Acme', orders: 12, revenue: 1940.50 },
{ customer: 'Globex', orders: 7, revenue: 820.00 }
];
$scope.exportExcel = function () {
var table = document.getElementById('tbl');
if (!table) {
throw new Error('The table #tbl does not exist when exportExcel ran.');
}
var workbook = XLSX.utils.table_to_book(table);
XLSX.writeFile(workbook, 'angularjs-table.xlsx');
};
}]);
The page must have loaded the SheetJS browser build before this controller runs, so the global XLSX object exists. Use the official SheetJS documentation above for the supported browser-loading method.
Export only the rows currently intended for download
DOM conversion includes the table that exists at export time. If your UI shows one page at a time, decide whether that is the desired file. To export all records, render an export-only table or use the data-driven method below.
Build an XLSX workbook from AngularJS data
SheetJS also documents creating a worksheet with XLSX.utils.json_to_sheet, appending it to a workbook and writing the file. This avoids depending on display filters and lets you choose stable column names.
$scope.exportDataExcel = function () {
var exportRows = $scope.data.map(function (item) {
return {
Customer: item.customer,
Orders: Number(item.orders),
Revenue: Number(item.revenue),
Date: item.date ? new Date(item.date) : null
};
});
var worksheet = XLSX.utils.json_to_sheet(exportRows, {
cellDates: true
});
var workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Report');
XLSX.writeFile(workbook, 'report-data.xlsx');
};
Use this path when the export contract is your application data rather than the rendered presentation.
Important SheetJS conversion options
| Option | What to check |
|---|---|
raw |
Controls whether values are interpreted or kept as raw text. |
dateNF |
Sets the number format used for dates. |
cellDates |
Controls whether dates are stored as date cells. |
sheetRows |
Limits the number of rows read from an HTML table. |
display |
Controls handling of hidden rows and cells. |
UTC |
Controls UTC versus local date interpretation. |
SheetJS parses numbers and stores other HTML-table values as strings by default. Inspect date output, hidden content and formatted currency before relying on the workbook for downstream calculations.
Export the table to PDF with jsPDF-AutoTable
jsPDF-AutoTable supports both an HTML selector or element and explicit JavaScript headings and rows. Its documented module form creates a jsPDF document, calls autoTable, then saves the PDF. See the jsPDF-AutoTable README for browser script-tag and module setup.
$scope.exportPdf = function () {
var table = document.getElementById('tbl');
if (!table) {
throw new Error('The table #tbl does not exist when exportPdf ran.');
}
var doc = new jsPDF();
autoTable(doc, { html: table });
doc.save('angularjs-table.pdf');
};
Connect the function to a button with ng-click="exportPdf()". The table must exist in the DOM when the click handler executes; exporting before AngularJS has rendered an ng-repeat produces an empty or missing table.
PDF from structured data
$scope.exportDataPdf = function () {
var body = $scope.data.map(function (item) {
return [item.customer, String(item.orders), String(item.revenue)];
});
var doc = new jsPDF();
autoTable(doc, {
head: [['Customer', 'Orders', 'Revenue']],
body: body
});
doc.save('report-data.pdf');
};
Structured rows are useful when the PDF should contain values that are not visible in the UI or when you need a deliberate column order. Compare the result with the HTML approach when cells contain links, icons, nested markup or formatted values.
AngularJS integration checklist
- Load AngularJS, the export library and your application code.
- Give the target table a stable ID such as
tbl. - Wait until the table has rendered its
ng-repeatrows. - Choose DOM export or controller-data export deliberately.
- Normalize dates, numbers, nulls and currency strings.
- Test empty results, long text, hidden columns and large row counts.
- Tell users whether export contains all records or only the visible page.
Common errors and fixes
| Error or symptom | Cause | Fix |
|---|---|---|
XLSX is not defined |
SheetJS was not loaded or loaded after the controller. | Load the supported SheetJS browser build before application code and check the network tab. |
| Empty Excel or PDF file | The table has not rendered, the selector is wrong, or the result set is empty. | Run export from a button after rendering; verify document.getElementById('tbl') and row count. |
| Only one page exported | The DOM contains only the current pagination page. | Export the complete data array or render a separate all-rows export table. |
| Numbers behave like text | Values include currency symbols, separators or other formatting. | Use data-driven export and convert values with Number; review the raw option. |
| Dates shift by a day | Local and UTC conversion differs. | Choose a consistent timezone policy and review UTC, cellDates and dateNF. |
| PDF columns overflow | Too many columns or long unbroken text for the page size. | Export fewer columns, shorten displayed values, or use structured rows and the table options documented by AutoTable. |
| Angular expressions appear literally | Export ran before the digest/render completed. | Trigger from a user action after data binding, or schedule export after the data promise resolves and the view has rendered. |
| Download blocked by browser policy | Popup/download restrictions or an insecure page context. | Start the download from a user gesture and serve the application over HTTPS. |
Performance, reliability and cost
- DOM conversion walks every selected row and cell. Large tables can block the main thread while the workbook or PDF is generated.
- For large datasets, prefer controller-data export, limit columns, and avoid rendering thousands of rows solely to export them.
- Do not promise that CSS layout, icons, interactive controls or virtualized rows will appear in XLSX or PDF exactly as displayed.
- Client-side exports use the browser’s memory. A very large workbook can fail before download; consider server-side generation when your dataset exceeds practical browser limits.
- Validate the downloaded file in the browsers and spreadsheet/PDF readers your users support. The cited documentation does not establish an AngularJS compatibility guarantee for every runtime.
- These libraries generate files in the user’s browser, so there is no per-file API charge. Your infrastructure still pays the normal cost of loading the page and its data.
Or skip the browser setup
If your real goal is a screenshot or PDF of a public report page, ScreenshotNeo handles the capture with one GET request. Its cleanup steps accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result.
See the ScreenshotNeo API documentation for all options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://your-app.example.com/report -o report.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://your-app.example.com/report"},
timeout=90,
)
r.raise_for_status()
open("report.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://your-app.example.com/report'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
require('fs').writeFileSync('report.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo also supports PDF output, full-page capture with lazy images loaded, element capture by CSS selector, custom CSS and JavaScript, waits, headers, cookies, authentication, timezone and geolocation, blocking rules, caching, signed links, asynchronous jobs, webhooks and bulk capture. An MCP server lets AI agents use take_screenshot, get_page_info and capture_pdf. There are 1,000 free screenshots each month with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account and start with 1,000 screenshots per month at no charge.
FAQ
Can I export an AngularJS table without jQuery?
Yes. The SheetJS and jsPDF-AutoTable flows above use the DOM element directly. Follow each project’s documented browser setup.
Should I export filtered rows?
Use the DOM path only when the visible filtered table is exactly what users should receive. Otherwise filter the controller data and build the workbook or PDF from that array.
Can XLSX preserve my table’s CSS?
No. HTML-to-XLSX conversion reads table content and interprets values; it is not a full CSS layout export. For visual fidelity, produce a PDF or screenshot.
Why does my PDF differ from the browser view?
AutoTable creates a document table from HTML or data. Browser CSS, responsive layout and interactive controls are not guaranteed to map one-to-one.
When should I use ScreenshotNeo?
Use it when you need a reliable rendered page image or PDF without maintaining a headless-browser capture service, especially when consent banners, popups, bot checks or failed loads affect captures.


