Getting Started
The headless libraries give you the engine and the plugin system — you own every pixel of the UI. An app depends on two packages: the adapter for your framework, and an engine.
Installation#
npm install @embedpdf/angular @embedpdf/engineThe Angular adapter ships each vertical as a secondary entry point — Angular’s native library modularity — with inject functions instead of hooks.
Your first viewer#
localEngine() creates the engine — synchronously, allocating nothing until
first use, so a module-scope const engine = localEngine() is safe (even under
SSR). Hand it to <Viewer>: the viewer warms it up on mount and PDFium boots
in a worker in the background, so the stage and render layer draw the pages the
moment a document is ready — no worker wiring, no lifecycle to manage. This is
the whole app:
Loading live preview…
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { localEngine } from '@embedpdf/engine';
import { EpdfViewer, injectDocumentId } from '@embedpdf/angular/runtime';
import type { EpdfInitialDocument, OpenInput } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, stagePlugin } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, renderPlugin } from '@embedpdf/angular/render';
// The local engine opens bytes: fetch lazily, under the loading tab.
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
// Kernel readers live INSIDE <epdf-viewer>, where the host is injectable —
// and document UI is gated on having a document.
@Component({
selector: 'demo-workspace',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [EpdfStage, EpdfPageTemplate, EpdfRenderLayer],
template: `
@if (documentId()) {
<epdf-stage style="display: block; height: 100%">
<ng-template epdfPage>
<epdf-render-layer />
</ng-template>
</epdf-stage>
} @else {
<p>Loading…</p>
}
`,
})
export class Workspace {
readonly documentId = injectDocumentId();
}
@Component({
selector: 'demo-root',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [EpdfViewer, Workspace],
template: `
<epdf-viewer [engine]="engine" [plugins]="plugins" [initialDocuments]="initialDocuments">
<div style="height: 500px">
<demo-workspace />
</div>
</epdf-viewer>
`,
})
export class App {
// `localEngine()` IS the engine — created synchronously, costing nothing
// until first use. The viewer warms it up when the kernel materializes;
// PDFium boots in a worker in the background, and only opening a document
// awaits it — the UI renders at t≈0.
readonly engine = localEngine();
readonly plugins = [stagePlugin(), renderPlugin()];
readonly initialDocuments: EpdfInitialDocument[] = [{ source: ebook }];
}Prefer the viewer to own the engine’s lifetime — created on mount, destroyed on
unmount? Pass a thunk instead: engine={() => localEngine()}. See the
Engine getting started for the ownership model,
fallback fonts, cloud, and SSR.
Your feedback goes directly to the documentation team.