Dokumentation

Service & Vorschau

Einrichtung

provideJasy() ist optional. Ohne funktioniert inject(JasyPdf) mit den Standardschriften und den Standardwerten der Engine. Es gehört in die app.config.ts: jasy ist ein Service für die ganze App, provideJasy() in den Providern einer Lazy Route ändert ihn also nicht.

// app.config.ts
import { provideJasy } from "@jasy/angular";

export const appConfig: ApplicationConfig = {
  providers: [
    provideJasy({
      // Every document can use these by name: Text("...", { font: "Inter" }).
      fonts: {
        Inter: { normal: "/fonts/Inter-Regular.woff2", bold: "/fonts/Inter-Bold.woff2" },
      },
      // Applied to every PDF; the options of a single call win.
      defaults: { accessible: true },
      // Load the font data once the app is idle, so the first PDF does not wait for it.
      preload: true,
    }),
  ],
};
OptionWas es tut
fontsSchriften nach Namen: eine URL oder Bytes, oder eine Familie { normal, bold?, italic?, boldItalic? }
defaultsRender-Optionen für jedes PDF, etwa accessible oder encrypt
preloaddie Schriftdaten laden, sobald die App gestartet und der Browser untätig ist, statt mit dem ersten PDF

Eine Schrift-URL wird geladen, wenn ein PDF sie zum ersten Mal braucht, und bleibt für die Laufzeit der App. .ttf, .woff und .woff2 funktionieren alle. Schlägt ein Request fehl, versucht es das nächste PDF neu.

Eine URL, hier und bei einem Formular, geht über den HttpClient von Angular, Ihre Interceptors greifen also: ein Auth-Header, eine Basis-URL. Ab Angular 21 gibt es ihn immer; unter 20 ohne provideHttpClient() nutzt jasy fetch, und ein 401 oder 403 sagt, dass das der Grund ist.

Ein Dokument

PdfDocumentFn<T> ist der Typ eines Dokuments: (data: T) => Document(...), synchron oder asynchron. PdfDocumentFn ohne Typargument ist ein Dokument, das keine Daten nimmt.

Ein Dokument läuft in einem Injection Context, wie funktionale Guards und Resolver. inject(LOCALE_ID) gibt Ihnen die Sprache der App, und inject() erreicht Ihre Services. In einer pdfResource läuft es im Injector seiner Komponente, sieht also auch, was die Komponente und ihre Route bereitstellen. Auch ein getaggtes PDF übernimmt seine Sprache aus LOCALE_ID.

inject(JasyPdf)

MethodeWas sie tut
render(document, data, options?)Die PDF-Bytes, ein Uint8Array. Im Browser und auf dem Server.
blob(document, data, options?)Das PDF als Blob, bereit für einen Upload.
download(document, data, filename?, options?)Speichert das PDF im Browser.
open(document, data, options?)Öffnet das PDF in einem neuen Tab, aus einem Click-Handler.
download(bytes, filename?) · open(bytes)Dasselbe für ein fertiges PDF, etwa ein ausgefülltes Formular.
preload()Lädt die Schriftdaten jetzt, etwa beim Hover, damit das nächste PDF nicht wartet.

options sind die Render-Optionen der Engine: { encrypt: { userPassword } } für AES-256, { accessible: true } für ein getaggtes PDF, onDiagnostic für das, was die Engine zu sagen hat. Sie werden über die defaults von provideJasy gelegt.

Ein Dokument ohne Daten braucht keine: render(doc), download(doc, undefined, "a.pdf"). Über den Service aufgerufen, läuft ein Dokument im Root-Injector der App.

Zwei Fehler, die Sie am Namen abfangen können:

  • JasyBrowserOnlyError - download() und open() auf dem Server, wo es keinen Tab zum Speichern gibt. Nutzen Sie dort render().
  • JasyPopupBlockedError - der Browser hat den neuen Tab blockiert, weil open() nicht direkt aus einem Klick kam. Ihre App bleibt, wo sie ist; download() funktioniert immer.

pdfResource()

pdfResource(document, data?, { options?, injector? })

Ein PDF, das Ihren Signals folgt. Es ist eine ResourceRef von Angular - value(), isLoading(), error(), reload() - plus latest(), das letzte PDF, das fertig gerendert wurde.

  • data wird gelesen wie resource({ params }): Ändert sich ein Signal darin, rendert das PDF neu, und ein Ergebnis, das ein neueres ersetzt hat, wird verworfen. Geben Sie undefined zurück, um zu warten, etwa bis ein Input einen Wert hat. Ein Dokument ohne Daten nimmt keine: pdfResource(doc).
  • options kann eine Funktion sein und wird wie data verfolgt. Ein Signal darin rendert das PDF neu.
  • injector ist für einen Aufruf außerhalb eines Injection Context.

Ein gerendertes PDF hält seine bytes, seine blob:-URL als url und safeUrl, das direkt in <iframe [src]> passt. Die alte URL wird freigegeben, wenn eine neue sie ersetzt, und die letzte, wenn die Komponente zerstört wird.

Binden Sie eine Vorschau an latest(), nicht an value(). Angular leert value() bei jeder Änderung der Daten, ein <iframe> hinter hasValue() würde also bei jedem Tastendruck entfernt und neu angelegt und verlöre Scroll-Position und Zoom. latest() behält das letzte PDF, während das nächste rendert, und wenn eines fehlschlägt:

@if (preview.latest(); as pdf) {
<iframe [src]="pdf.safeUrl" title="Preview"></iframe>
} @if (preview.isLoading()) {
<p>Rendering ...</p>
} @if (preview.error(); as error) {
<p>{{ error.message }}</p>
}

pdfResource() baut auf resource() von Angular auf, das Angular in 20 und 21 als experimentell markiert und ab 22 als stabil. Der Service hängt nicht davon ab.

Auf dem Server

render() und blob() funktionieren in Node wie im Browser, ein Server kann die Bytes also erzeugen und mit dem Content-Type application/pdf senden. Eine pdfResource bleibt auf dem Server untätig, denn eine Vorschau hat dort niemanden, der sie ansieht; im Browser rendert sie nach der Hydration.

In einer SSR-App erlaubt ng add zusätzlich die CommonJS-Module von jimp, das der Server für Bilder nutzt.

Bundle-Größe

@jasy/angular legt die Engine nicht in Ihr Start-Bundle; Ihre Dokumente bringen sie mit, wo immer sie importiert werden. Die Schriftdaten (158 KB minifiziert, 12 KB mit gzip) laden mit dem ersten PDF, oder früher mit preload.

Legen Sie das PDF-Feature also hinter @defer oder eine Lazy Route. Eine neue Angular-20-App mit provideJasy() startet dann mit 231 KB. Direkt in der Root-Komponente gerendert, startet sie mit 522 KB, knapp über der Standardwarnung von Angular bei 500 KB.

@Component({
  selector: "app-billing",
  imports: [InvoicePreview],
  template: `
    @defer (on interaction) {
      <app-invoice-preview [invoice]="invoice()" />
    } @placeholder {
      <button>Show the invoice</button>
    }
  `,
})
export class Billing {
  readonly invoice = input.required<Invoice>();
}

Tests

JasyPdf und JasyForms sind Injection Tokens wie alle anderen:

TestBed.configureTestingModule({
  providers: [{ provide: JasyPdf, useValue: { download: vi.fn() } }],
});