Write PDFs as Angular components, for Angular. A document is a normal component whose template
uses the pdf-* elements; ngpdf lays it out, breaks it into pages and writes the PDF in the
browser. The layout and PDF engine is loaded on the first render, so importing @ngpdf/core adds
at most 15 KB gzipped to your initial bundle.
Docs, live demo and examples: ngpdf.dev
npm install @ngpdf/coreIt needs @angular/core 22 or newer, and works zoneless and with zone.js.
import { DatePipe } from '@angular/common';
import { Component, inject, input } from '@angular/core';
import { PDF_ELEMENTS, PdfRenderer } from '@ngpdf/core';
@Component({
selector: 'app-communicators-list',
imports: [PDF_ELEMENTS, DatePipe],
template: `
<pdf-document title="Lista de comunicadores">
<pdf-page style="padding: 36; font-size: 9">
<pdf-table [columns]="[30, '2fr', '3fr', '1fr', '1fr']">
<pdf-row
header
style="padding-top: 5; padding-bottom: 5; background-color: #dd0031; color: #ffffff"
>
<pdf-cell style="padding-left: 4"><pdf-text>#</pdf-text></pdf-cell>
<pdf-cell style="padding-left: 4"><pdf-text>Nome</pdf-text></pdf-cell>
<pdf-cell style="padding-left: 4"><pdf-text>E-mail</pdf-text></pdf-cell>
<pdf-cell style="padding-left: 4"><pdf-text>Telefone</pdf-text></pdf-cell>
<pdf-cell style="padding-left: 4"><pdf-text>Cadastro</pdf-text></pdf-cell>
</pdf-row>
@for (item of items(); track item.id) {
<pdf-row
style="padding-top: 4; padding-bottom: 4"
[style.background-color]="$odd ? '#f2f2f2' : '#ffffff'"
>
<pdf-cell style="padding-left: 4"
><pdf-text>{{ item.id }}</pdf-text></pdf-cell
>
<pdf-cell style="padding-left: 4"
><pdf-text>{{ item.name }}</pdf-text></pdf-cell
>
<pdf-cell style="padding-left: 4"
><pdf-text>{{ item.email }}</pdf-text></pdf-cell
>
<pdf-cell style="padding-left: 4"
><pdf-text>{{ item.phone }}</pdf-text></pdf-cell
>
<pdf-cell style="padding-left: 4">
<pdf-text>{{ item.registeredAt | date: 'dd/MM/yyyy' }}</pdf-text>
</pdf-cell>
</pdf-row>
}
</pdf-table>
<pdf-page-number format="Página {page} de {total}" style="text-align: right" />
</pdf-page>
</pdf-document>
`,
})
export class CommunicatorsList {
readonly items = input.required<Communicator[]>();
}
// Anywhere with an injection context:
const pdf = inject(PdfRenderer);
await pdf.download(CommunicatorsList, { inputs: { items }, fileName: 'communicators.pdf' });
// or: const blob = await pdf.toBlob(CommunicatorsList, { inputs: { items } });The header row repeats on every page, rows are never split, and the text stays selectable.
- Standard PDF fonts only (WinAnsi). A character outside it is drawn as
?, with a warning in development mode. pdf-page-numberalways sits at the bottom of the content area.- Inside a
pdf-table, horizontal padding, borders and margins on a row are ignored. Put horizontal padding on thepdf-cells. - A unit taller than a page (a row, or a
pdf-viewin a column) is clipped to its page. border-radiustogether with borders draws square border lines.- A bare number is in points, except in
line-height, where it multiplies the font size as in CSS (line-height: 1.4). Writeline-height: 14ptfor points. - Text sits at the top of its line box: extra
line-heightgoes below the text, not half above and half below as in CSS. - Bare text inside an element other than
pdf-text(<pdf-cell>{{ x }}</pdf-cell>) is laid out as an anonymouspdf-textwith its parent's text styles. - Your own components inside a document are transparent, like
display: contents: theirpdf-*elements are laid out as if written in place, and styles on the component's host element are ignored with a warning. Style apdf-*element inside it instead. The same goes for the document component you render: style itspdf-documentorpdf-page, not its host. - Colors are hex (3, 4, 6 or 8 digits),
rgb()/rgba(), the CSS named colors andtransparent. Other forms, such ashsl(), throw. opacitymultiplies down the tree; a faded view's children are each faded, not composited as one group, so overlapping children show through each other.- A table header row is not repeated on a page whose first row fits only without it.
- Images fetched from a URL have no size limit; a very large image is downloaded in full.
The demo app in projects/demo builds three documents from made-up data: a
capture report, a communicators list and a licences list. Run it with npm start.
Each version is listed in projects/core/CHANGELOG.md and on the
releases page, with its SBOM. Versions on npm are
published from GitHub Actions with provenance,
which links each one to the commit and workflow that built it.
Thanks go to these people (emoji key):
Gabriel Silva 💻 📖 🎨 |
Questions and ideas: Discussions. Good first issues: here.
This project follows the all-contributors specification. Contributions of any kind are welcome: see CONTRIBUTING.md.
MIT. Security: see SECURITY.md.