---
title: "Quick start — Angular"
description: "Build a PDF viewer step by step, with text selection, zoom buttons, a highlighter and a save button."
framework: "Angular"
source: "https://www.cloudpdf.com/docs/headless/angular/quick-start"
---

# Quick start

This guide builds a small PDF viewer: the pages, text people can select, zoom buttons, a
highlighter and a save button. Each step adds one plugin or one control, so you can stop at any
step and have a working viewer.

## 1. Install

You need two packages: the one for your framework, and an engine.

```sh
pnpm add @embedpdf/angular@next @cloudpdf/engine@next
```

Every feature is a separate import from the framework package, such as `@embedpdf/angular/stage`,
so what you don't use never reaches your bundle.

## 2. Show a PDF

Create the engine once, then give it to `provideEmbedPdf()` with the plugins you want and the document to
open. `<epdf-stage>` shows the pages, and `<epdf-render-layer>` draws each one:

**`basic.ts`**

```typescript
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { cloudEngine } from '@cloudpdf/engine';

const welcome: OpenInput = { kind: 'grant', grant: 'shr_gvpJcG1ULUMon19C0D1wODSY' };

// The component that provides the viewer renders it: the engine is created in the browser,
// and costs nothing until the first document opens, so the page renders at once.
@Component({
  selector: 'demo-root',
  imports: [EpdfDocumentGate, EpdfStage, EpdfPageTemplate, EpdfRenderLayer],
  providers: [
    provideEmbedPdf(
      { engine: () => cloudEngine(), initialDocuments: [{ source: welcome }] },
      withStage(),
      withRender(),
    ),
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div style="height: 500px">
      <!-- Document UI is defined over a document: gate it on having one. -->
      <epdf-stage *epdfDocumentGate="let document; fallback: loading" style="height: 100%">
        <ng-template epdfPage>
          <epdf-render-layer />
        </ng-template>
      </epdf-stage>
    </div>

    <ng-template #loading><p>Loading…</p></ng-template>
  `,
})
export class App {}
```

`cloudEngine()` holds an HTTP client and nothing else, so creating it at the top of a
module is safe, even with server-side rendering. Every page, text and annotation call goes to your
CloudPDF server; see [Setup](setup).

`*epdfDocumentGate` shows its `fallback` until the document is open, so the Stage never renders
without one.

## 3. Let people select text

Add the interaction plugin, which handles the pointer for every feature, and the selection plugin.
Then put `<epdf-selection-layer>` above the page:

**`select-text.ts`**

```typescript
import { Component } from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf, type OpenInput } from '@embedpdf/angular/runtime';
import { withInteraction } from '@embedpdf/angular/interaction';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { EpdfSelectionLayer, withSelection } from '@embedpdf/angular/selection';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { cloudEngine } from '@cloudpdf/engine';

const welcome: OpenInput = { kind: 'grant', grant: 'shr_gvpJcG1ULUMon19C0D1wODSY' };

@Component({
  selector: 'app-root',
  imports: [EpdfDocumentGate, EpdfStage, EpdfPageTemplate, EpdfRenderLayer, EpdfSelectionLayer],
  providers: [
    provideEmbedPdf(
      {
        engine: () => cloudEngine(),
        initialDocuments: [{ source: welcome }],
      },
      withStage(),
      withRender(),
      withInteraction(),
      withSelection(),
    ),
  ],
  template: `
    <epdf-stage *epdfDocumentGate style="height: 500px">
      <ng-template epdfPage>
        <epdf-render-layer />
        <epdf-selection-layer />
      </ng-template>
    </epdf-stage>
  `,
})
export class App {}
```

Drag over text to select it, double-click a word, or triple-click a line. On a phone, long-press a
word. See [Selecting & copying](text/selection) for copying and a selection menu.

## 4. Add zoom buttons

Every plugin has a service with its methods and its state as signals, and the Stage has
`EpdfStageRef`. Next to the Stage in a template, its template reference, `#stage="epdfStage"`,
gives you both:

**`zoom-controls.ts`**

```typescript
import { PercentPipe } from '@angular/common';
import { Component } from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf, type OpenInput } from '@embedpdf/angular/runtime';
import { withInteraction } from '@embedpdf/angular/interaction';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { EpdfSelectionLayer, withSelection } from '@embedpdf/angular/selection';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { cloudEngine } from '@cloudpdf/engine';

const welcome: OpenInput = { kind: 'grant', grant: 'shr_gvpJcG1ULUMon19C0D1wODSY' };

@Component({
  selector: 'app-root',
  imports: [
    EpdfDocumentGate,
    EpdfStage,
    EpdfPageTemplate,
    EpdfRenderLayer,
    EpdfSelectionLayer,
    PercentPipe,
  ],
  providers: [
    provideEmbedPdf(
      {
        engine: () => cloudEngine(),
        initialDocuments: [{ source: welcome }],
      },
      withStage(),
      withRender(),
      withInteraction(),
      withSelection(),
    ),
  ],
  template: `
    <section *epdfDocumentGate>
      <div>
        <button (click)="stage.zoomOut()">−</button>
        <span>{{ stage.zoomLevel() | percent }}</span>
        <button (click)="stage.zoomIn()">+</button>
      </div>

      <epdf-stage #stage="epdfStage" style="height: 500px">
        <ng-template epdfPage>
          <epdf-render-layer />
          <epdf-selection-layer />
        </ng-template>
      </epdf-stage>
    </section>
  `,
})
export class App {}
```

The buttons go inside `*epdfDocumentGate`, next to the stage they name. The percentage updates when
the zoom changes, whether a button, a pinch or code changed it.

## 5. Add a highlighter

The annotation plugin shows the annotations a PDF already has, and lets people add their own. Add
it with its layer, and a button that turns on the highlight tool:

**`highlighter.ts`**

```typescript
import { Component, computed, inject } from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf, type OpenInput } from '@embedpdf/angular/runtime';
import { EpdfAnnotationLayer, withAnnotation } from '@embedpdf/angular/annotation';
import { EpdfInteraction, withInteraction } from '@embedpdf/angular/interaction';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { EpdfSelectionLayer, withSelection } from '@embedpdf/angular/selection';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { cloudEngine } from '@cloudpdf/engine';

const welcome: OpenInput = { kind: 'grant', grant: 'shr_gvpJcG1ULUMon19C0D1wODSY' };

@Component({
  selector: 'app-root',
  imports: [
    EpdfDocumentGate,
    EpdfStage,
    EpdfPageTemplate,
    EpdfRenderLayer,
    EpdfSelectionLayer,
    EpdfAnnotationLayer,
  ],
  providers: [
    provideEmbedPdf(
      {
        engine: () => cloudEngine(),
        initialDocuments: [{ source: welcome }],
      },
      withStage(),
      withRender(),
      withInteraction(),
      withSelection(),
      withAnnotation(),
    ),
  ],
  template: `
    <button [attr.aria-pressed]="highlighting()" (click)="toggleHighlight()">Highlight</button>

    <epdf-stage *epdfDocumentGate style="height: 500px">
      <ng-template epdfPage>
        <epdf-render-layer />
        <epdf-selection-layer />
        <epdf-annotation-layer />
      </ng-template>
    </epdf-stage>
  `,
})
export class App {
  private readonly interaction = inject(EpdfInteraction);
  protected readonly highlighting = computed(() => this.interaction.activeToolId() === 'highlight');

  protected toggleHighlight() {
    this.interaction.activateTool(this.highlighting() ? 'pointer' : 'highlight');
  }
}
```

With the tool on, select text and it's highlighted. Click a highlight with the `pointer` tool to
select it. The page picture leaves the annotations out by itself, because the annotation layer
draws them now. The component that provides the viewer can inject its services
too, so the button needs no component of its own. See [Annotations](annotations) for every
tool.

## 6. Save

`documents.download()` gives you the PDF with every change, as bytes. `saveFile` hands them to the
browser as a download:

**`save-button.ts`**

```typescript
import { Component, inject } from '@angular/core';
import { EpdfDocuments, saveFile } from '@embedpdf/angular/runtime';

@Component({
  selector: 'app-save-button',
  template: `<button (click)="save()">Save</button>`,
})
export class SaveButton {
  private readonly documents = inject(EpdfDocuments);

  protected async save() {
    saveFile(await this.documents.download(), 'reviewed.pdf');
  }
}
```


Put `<app-save-button />` anywhere in the template of the component that provides
the viewer: it injects the service from there.

You don't need to save to keep the highlights: the server stored each one as it was made.
`download()` is for when you want the file itself.

## Next steps

- [Setup](setup) — Server-side rendering, bundlers, fonts, and hosting the engine yourself.
- [How it works](concepts/how-it-works) — Plugins, how your UI talks to them, layers, and which document a component talks to.
- [Scroll, zoom & layout](viewing/stage) — Layouts, zoom modes, page navigation and touch.
