---
title: "Several documents — Angular"
description: "Open several documents at once: tabs, side-by-side panes, and which document each part of your UI talks to."
framework: "Angular"
source: "https://www.embedpdf.com/docs/headless/angular/documents/tabs"
---

# Several documents

A viewer can hold several documents at once, like tabs in a browser. One of them is the active
document, and every component talks to it unless you say otherwise:

**`tabs.ts`**

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

@Component({
  selector: 'app-tabs',
  template: `
    <div role="tablist">
      @for (document of documents.documents(); track document.id) {
        <div>
          <button
            role="tab"
            [attr.aria-selected]="document.id === documents.activeId()"
            (click)="documents.setActive(document.id)"
          >
            {{ document.name }}
          </button>
          <button [attr.aria-label]="'Close ' + document.name" (click)="documents.close(document.id)">
            ×
          </button>
        </div>
      }
    </div>
  `,
})
export class Tabs {
  protected readonly documents = inject(EpdfDocuments);
}
```

Each document keeps its own state: its zoom and scroll position, its selection, its search and its
active tool. Switch tabs and back, and everything is where you left it.

**`tabs.ts`**

```typescript
import {
  ChangeDetectionStrategy,
  Component,
  inject,
  input,
  ViewEncapsulation,
} from '@angular/core';
import {
  EpdfDocument,
  EpdfDocumentGate,
  EpdfDocuments,
  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 { localEngine } from '@embedpdf/engine';

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
};

@Component({
  selector: 'demo-tabs',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="tabs" role="tablist">
      @for (document of documents.documents(); track document.id) {
        <div class="tab" [attr.data-active]="document.id === documents.activeId()">
          <button
            type="button"
            role="tab"
            class="name"
            [attr.aria-selected]="document.id === documents.activeId()"
            (click)="documents.setActive(document.id)"
          >
            {{ document.name }}
          </button>
          <button
            type="button"
            class="close"
            [attr.aria-label]="'Close ' + document.name"
            (click)="documents.close(document.id)"
          >
            ×
          </button>
        </div>
      }
      <button type="button" class="button" aria-label="Open another copy" (click)="openCopy()">
        +
      </button>
    </div>
  `,
})
export class Tabs {
  protected readonly documents = inject(EpdfDocuments);

  protected openCopy() {
    void this.documents.open(ebook, { name: `Copy ${this.documents.documents().length + 1}` });
  }
}

// The active tab: rename it, move it to the front, and zoom it. Each tab keeps its own zoom.
@Component({
  selector: 'demo-tab-tools',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="toolbar">
      <input
        #field
        class="field"
        aria-label="Tab name"
        [value]="document.name() ?? ''"
        (input)="documents.rename(document.id(), field.value)"
      />
      <button type="button" class="button" (click)="documents.move(document.id(), 0)">
        Move to front
      </button>
      <button type="button" class="button" aria-label="Zoom out" (click)="stage().zoomOut()">
        −
      </button>
      <button type="button" class="button" aria-label="Zoom in" (click)="stage().zoomIn()">
        +
      </button>
      <output class="readout">{{ percent(stage().zoomLevel()) }}%</output>
    </div>
  `,
})
export class TabTools {
  readonly stage = input.required<EpdfStage>();
  protected readonly documents = inject(EpdfDocuments);
  protected readonly document = inject(EpdfDocument);

  protected percent(zoomLevel: number): number {
    return Math.round(zoomLevel * 100);
  }
}

@Component({
  selector: 'demo-root',
  imports: [EpdfDocumentGate, EpdfStage, EpdfPageTemplate, EpdfRenderLayer, Tabs, TabTools],
  providers: [
    provideEmbedPdf(
      {
        engine: () => localEngine(),
        initialDocuments: [
          { source: ebook, name: 'Contract' },
          { source: ebook, name: 'Report' },
        ],
      },
      withStage(),
      withRender(),
    ),
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  styleUrl: './tabs.css',
  encapsulation: ViewEncapsulation.None,
  template: `
    <demo-tabs />
    <ng-container *epdfDocumentGate="let document; fallback: opening">
      <demo-tab-tools [stage]="stage" />
      <epdf-stage #stage="epdfStage" class="stage">
        <ng-template epdfPage>
          <epdf-render-layer />
        </ng-template>
      </epdf-stage>
    </ng-container>

    <ng-template #opening><p class="loading">Opening…</p></ng-template>
  `,
})
export class App {}
```

**`tabs.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The tab strip: one tab per document, then the button that opens another */
.tabs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 8px;
}

.tab {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--surface);
  box-shadow: 0 1px 2px var(--shadow);
}

.tab[data-active='true'] {
  background: var(--tint);
  border-color: var(--focus);
}

.tab button {
  font: inherit;
  font-size: 13px;
  color: var(--muted);
  background: none;
  border: 0;
  padding: 7px 10px;
  cursor: pointer;
}

.tab[data-active='true'] .name {
  color: var(--ink);
  font-weight: 600;
}

.tab .close {
  padding-left: 0;
  font-size: 15px;
  line-height: 1;
}

.tab button:focus-visible,
.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 9px;
}

/* What you do to the active tab */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 7px 12px;
  cursor: pointer;
  box-shadow: 0 1px 2px var(--shadow);
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.field {
  appearance: none;
  flex: 0 1 180px;
  min-width: 0;
  font: inherit;
  font-size: 13px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 7px 10px;
}

.field:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.readout {
  margin-left: auto;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The pages */
.stage {
  height: 400px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

## Manage the tabs

```ts
documents.setActive(id);
documents.rename(id, 'Signed contract');
documents.move(id, 0); // to the front
await documents.close(id);
```

Each entry in `documents`, from `inject(EpdfDocuments)`, has the document's `id`, its `name`, and its
`status`: `'loading'`, `'locked'`, `'ready'` or `'error'`, so a tab can show a spinner or a lock.
Every field is listed under [State](#state).

## Talk to a specific document

A component talks to the active document. Inside `[epdfDocumentScope]`, it talks to the one you name,
whatever is active:

**`document-scope.ts`**

```typescript
import { Component, input } from '@angular/core';
import { EpdfDocumentScope } from '@embedpdf/angular/runtime';
import { EpdfRenderLayer } from '@embedpdf/angular/render';
import { EpdfPageTemplate, EpdfStage } from '@embedpdf/angular/stage';
import { ZoomControls } from './zoom-controls';

@Component({
  selector: 'app-document-view',
  imports: [EpdfDocumentScope, EpdfStage, EpdfPageTemplate, EpdfRenderLayer, ZoomControls],
  template: `
    <div [epdfDocumentScope]="documentId()">
      <epdf-stage>
        <ng-template epdfPage>
          <epdf-render-layer />
        </ng-template>
      </epdf-stage>
      <app-zoom-controls />
    </div>
  `,
})
export class DocumentView {
  readonly documentId = input.required<string>();
}
```

Everything inside, such as the Stage, `inject(EpdfSearch)` and `inject(EpdfSelection)`, uses that document.
That's how you show two documents at once, or a preview of one while another is active.

**`scope.ts`**

```typescript
import {
  ChangeDetectionStrategy,
  Component,
  inject,
  input,
  ViewEncapsulation,
} from '@angular/core';
import {
  EpdfDocument,
  EpdfDocumentGate,
  EpdfDocuments,
  EpdfDocumentScope,
  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 { localEngine } from '@embedpdf/engine';

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
};

// The same component in both panes: each talks to the document its [epdfDocumentScope] names.
@Component({
  selector: 'demo-header',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="header">
      <strong>{{ document.name() }}</strong>
      {{ document.pageCount() }} pages
      <button type="button" class="button" aria-label="Zoom out" (click)="stage().zoomOut()">
        −
      </button>
      <output class="readout">{{ percent(stage().zoomLevel()) }}%</output>
      <button type="button" class="button" aria-label="Zoom in" (click)="stage().zoomIn()">
        +
      </button>
    </div>
  `,
})
export class Header {
  readonly stage = input.required<EpdfStage>();
  protected readonly document = inject(EpdfDocument);

  protected percent(zoomLevel: number): number {
    return Math.round(zoomLevel * 100);
  }
}

@Component({
  selector: 'demo-root',
  imports: [
    EpdfDocumentScope,
    EpdfDocumentGate,
    EpdfStage,
    EpdfPageTemplate,
    EpdfRenderLayer,
    Header,
  ],
  providers: [
    provideEmbedPdf(
      {
        engine: () => localEngine(),
        initialDocuments: [
          { source: ebook, name: 'Original' },
          { source: ebook, name: 'Revised' },
        ],
      },
      withStage(),
      withRender(),
    ),
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  styleUrl: './scope.css',
  encapsulation: ViewEncapsulation.None,
  template: `
    <div class="split">
      @for (document of documents.documents(); track document.id) {
        <section class="pane" [epdfDocumentScope]="document.id">
          <ng-container *epdfDocumentGate="let ready; fallback: opening">
            <demo-header [stage]="stage" />
            <epdf-stage #stage="epdfStage" class="stage">
              <ng-template epdfPage>
                <epdf-render-layer />
              </ng-template>
            </epdf-stage>
          </ng-container>
        </section>
      }
    </div>

    <ng-template #opening><p class="loading">Opening…</p></ng-template>
  `,
})
export class App {
  protected readonly documents = inject(EpdfDocuments);
}
```

**`scope.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* Two documents: stacked, and side by side where there's room */
.split {
  container-type: inline-size;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.pane {
  flex: 1 1 100%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

@container (min-width: 620px) {
  .pane {
    flex: 1 1 0;
  }
}

/* A pane's header: its document, and its own zoom */
.header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--muted);
}

.header strong {
  margin-right: auto;
  color: var(--ink);
  font-weight: 600;
}

.button {
  min-width: 34px;
  font: inherit;
  font-size: 14px;
  font-weight: 550;
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 7px 8px;
  cursor: pointer;
  box-shadow: 0 1px 2px var(--shadow);
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.readout {
  min-width: 44px;
  text-align: center;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The pages */
.stage {
  height: 340px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

## Side by side

The view manager arranges documents into panes, each with its own tabs, like split editors in a
code editor. Register `withViewManager()`, then render one Stage per pane:

**`panes.ts`**

```typescript
import { Component, inject } from '@angular/core';
import { EpdfDocumentScope } from '@embedpdf/angular/runtime';
import { EpdfRenderLayer } from '@embedpdf/angular/render';
import { EpdfPageTemplate, EpdfStage } from '@embedpdf/angular/stage';
import { EpdfViewManager } from '@embedpdf/angular/view-manager';
import { PaneTabs } from './pane-tabs';

@Component({
  selector: 'app-panes',
  imports: [EpdfDocumentScope, EpdfStage, EpdfPageTemplate, EpdfRenderLayer, PaneTabs],
  template: `
    <div class="panes">
      @for (pane of views.panes(); track pane.id) {
        <section
          [attr.data-focused]="pane.id === views.focusedPaneId()"
          (focusin)="views.setFocusedPane(pane.id)"
        >
          <app-pane-tabs [pane]="pane" />
          @if (pane.activeDocumentId; as documentId) {
            <div [epdfDocumentScope]="documentId">
              <epdf-stage>
                <ng-template epdfPage>
                  <epdf-render-layer />
                </ng-template>
              </epdf-stage>
            </div>
          }
        </section>
      }
    </div>
  `,
})
export class Panes {
  protected readonly views = inject(EpdfViewManager);
}
```

```ts
views.splitPane(documentId); // the document in a new pane, beside the focused one
views.setActiveDocument(paneId, documentId); // show another of the pane's tabs
views.moveDocumentBetween(fromPaneId, toPaneId, documentId); // drag a tab to another pane
views.removePane(paneId); // its documents move to another pane
```

A document opened with `documents.open()` lands in the focused pane. Every call is listed under
[Methods](#methods).

**`panes.ts`**

```typescript
import {
  ChangeDetectionStrategy,
  Component,
  effect,
  inject,
  untracked,
  ViewEncapsulation,
} from '@angular/core';
import {
  EpdfDocumentGate,
  EpdfDocuments,
  EpdfDocumentScope,
  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 { EpdfViewManager, withViewManager, type PaneInfo } from '@embedpdf/angular/view-manager';
import { localEngine } from '@embedpdf/engine';

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
};

@Component({
  selector: 'demo-root',
  imports: [EpdfDocumentScope, EpdfDocumentGate, EpdfStage, EpdfPageTemplate, EpdfRenderLayer],
  providers: [
    provideEmbedPdf(
      {
        engine: () => localEngine(),
        initialDocuments: [
          { source: ebook, name: 'Contract' },
          { source: ebook, name: 'Report' },
        ],
      },
      withStage(),
      withRender(),
      withViewManager(),
    ),
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  styleUrl: './panes.css',
  encapsulation: ViewEncapsulation.None,
  template: `
    <div class="panes">
      @for (pane of views.panes(); track pane.id) {
        <section
          class="pane"
          [attr.data-focused]="pane.id === views.focusedPaneId()"
          (pointerdown)="views.setFocusedPane(pane.id)"
        >
          <div class="bar" role="tablist">
            @for (id of pane.documentIds; track id) {
              <button
                type="button"
                role="tab"
                class="tab"
                [attr.aria-selected]="id === pane.activeDocumentId"
                (click)="views.setActiveDocument(pane.id, id)"
              >
                {{ nameOf(id) }}
              </button>
            }
            <button
              type="button"
              class="button"
              [disabled]="!pane.activeDocumentId || pane.documentIds.length < 2"
              (click)="split(pane)"
            >
              Split
            </button>
            <button
              type="button"
              class="button"
              [disabled]="views.panes().length < 2"
              (click)="views.removePane(pane.id)"
            >
              Close pane
            </button>
          </div>
          @if (pane.activeDocumentId; as documentId) {
            <ng-container [epdfDocumentScope]="documentId">
              <epdf-stage *epdfDocumentGate="let ready; fallback: opening" class="stage">
                <ng-template epdfPage>
                  <epdf-render-layer />
                </ng-template>
              </epdf-stage>
            </ng-container>
          } @else {
            <p class="empty">No document in this pane.</p>
          }
        </section>
      }
    </div>

    <ng-template #opening><p class="empty">Opening…</p></ng-template>
  `,
})
export class App {
  protected readonly views = inject(EpdfViewManager);
  private readonly documents = inject(EpdfDocuments);

  constructor() {
    // On load, both documents land in the first pane: put the second one beside it, once.
    const splitOnLoad = effect(() => {
      const panes = this.views.panes();
      if (panes.length !== 1 || panes[0].documentIds.length !== 2) return;
      splitOnLoad.destroy();
      untracked(() => this.views.splitPane(panes[0].documentIds[1]));
    });
  }

  protected nameOf(id: string): string {
    return this.documents.documents().find((document) => document.id === id)?.name ?? id;
  }

  protected split(pane: PaneInfo) {
    if (pane.activeDocumentId) this.views.splitPane(pane.activeDocumentId, { from: pane.id });
  }
}
```

**`panes.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The panes: stacked, and side by side where there's room */
.panes {
  container-type: inline-size;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.pane {
  flex: 1 1 100%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--surface);
}

/* The focused pane is where documents you open land */
.pane[data-focused='true'] {
  border-color: var(--focus);
}

@container (min-width: 620px) {
  .pane {
    flex: 1 1 0;
  }
}

/* A pane's tabs, then what you do to the pane */
.bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.tab {
  font: inherit;
  font-size: 13px;
  color: var(--muted);
  background: none;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 6px 10px;
  cursor: pointer;
}

.tab[aria-selected='true'] {
  color: var(--ink);
  font-weight: 600;
  background: var(--tint);
  border-color: var(--line-strong);
}

.button {
  font: inherit;
  font-size: 12px;
  font-weight: 550;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 6px 10px;
  cursor: pointer;
}

.button:first-of-type {
  margin-left: auto;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.tab:focus-visible,
.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* The pages */
.stage {
  height: 320px;
  border-radius: 10px;
  background: var(--canvas);
}

.empty {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

## Methods

What `inject(EpdfDocuments)` gives you for several documents. Opening and closing are on
[Opening documents](opening#methods), downloading and printing on
[Saving & downloading](saving#methods).

| Method              | What it does                                                                                                                         |
| :------------------ | :----------------------------------------------------------------------------------------------------------------------------------- |
| `setActive(id)`     | Makes a document the active one: the one every component talks to outside `[epdfDocumentScope]`. [Manage the tabs](#manage-the-tabs) |
| `rename(id, name)`  | Changes the name its tab shows. The file doesn't change.                                                                             |
| `move(id, index)`   | Moves a document to another place in the tab order                                                                                   |
| `swap(id, otherId)` | Swaps two documents in the tab order                                                                                                 |
| `setOrder(ids)`     | Puts every document in a new order at once. `ids` holds the id of every open document.                                               |

Everything `inject(EpdfViewManager)` gives you.

| Method                                                          | What it does                                                                                                                                              |
| :-------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `splitPane(documentId, { from? })`                              | Moves an open document into a new pane beside the focused one, or beside `from`, and focuses it. Returns the new pane's id. [Side by side](#side-by-side) |
| `createPane({ documentIds? })`                                  | Adds a pane, empty or with these documents moved into it, and focuses it. Returns its id.                                                                 |
| `removePane(paneId, { moveDocumentsTo? })`                      | Closes a pane. Its documents move to `moveDocumentsTo`, or else to the focused or the last pane.                                                          |
| `movePane(paneId, index)`                                       | Moves a pane to another place in the order, for reordering panes by dragging                                                                              |
| `setFocusedPane(paneId)`                                        | Makes a pane the focused one, where new documents open. `null` focuses none.                                                                              |
| `setActiveDocument(paneId, documentId)`                         | Shows another of the pane's documents                                                                                                                     |
| `addDocument(paneId, documentId, index?)`                       | Adds a document to a pane's tabs. A document is in one pane at a time, so it leaves the pane it was in.                                                   |
| `removeDocument(paneId, documentId)`                            | Takes a document out of a pane, without closing it                                                                                                        |
| `moveDocumentWithin(paneId, documentId, index)`                 | Moves a tab to another place inside its pane                                                                                                              |
| `moveDocumentBetween(fromPaneId, toPaneId, documentId, index?)` | Moves a tab from one pane to another, for dragging tabs between panes                                                                                     |

## State

Every value is a signal on `inject(EpdfDocuments)`: read it in a template or a `computed()`, and your view updates when it changes. The method in the last column reads the same value once.

| Field         | What it is                                                                                                                                                   | Read once                                        |
| :------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------- |
| `documents()` | Every open document, in tab order, including ones that are loading, locked or failed. Each has the fields [`inject(EpdfDocument)`](opening#state) gives you. | `list()`, `has(id)`, `getCount()`, `getOrder()`  |
| `activeId()`  | The active document's id, or `null` with no document open                                                                                                    | `getActiveId()`, or `getActive()` for its fields |

`has(id)` says whether a document is open, `getCount()` how many are, and `getOrder()` gives just their
ids. One document's fields come from `get(id)`, on [Opening documents](opening#state).

Every value is a signal on `inject(EpdfViewManager)`: read it in a template or a `computed()`, and your view updates when it changes. The method in the last column reads the same value once.

| Field             | What it is                                                                                                           | Read once                                                                           |
| :---------------- | :------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------- |
| `panes()`         | Every pane, in order. Each has its `id`, its `documentIds` (its tabs, in order) and the `activeDocumentId` it shows. | `listPanes()`, `getPane(paneId)`, `getPaneOrder()`, `getPaneOfDocument(documentId)` |
| `focusedPaneId()` | The pane new documents open in, or `null`                                                                            | `getFocusedPaneId()`                                                                |

`getPane(paneId)` reads one pane, `getPaneOrder()` gives just their ids, and
`getPaneOfDocument(documentId)` says which pane holds a document.

## Events

**`active-route.ts`**

```typescript
import { Component, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Router } from '@angular/router';
import { EpdfDocuments } from '@embedpdf/angular/runtime';

@Component({ selector: 'app-document-route', template: '' })
export class DocumentRoute {
  private readonly router = inject(Router);

  constructor() {
    inject(EpdfDocuments)
      .activeChanged$.pipe(takeUntilDestroyed())
      .subscribe(({ documentId }) => this.router.navigate(['/documents', documentId], { replaceUrl: true }));
  }
}
```

| Event                         | Fires when                             | Carries                                |
| :---------------------------- | :------------------------------------- | :------------------------------------- |
| `activeChanged$`              | Another document became the active one | `documentId`, `previousDocumentId`     |
| View manager `paneCreated$`   | A pane was added                       | `paneId`                               |
| View manager `paneRemoved$`   | A pane was closed                      | `paneId`                               |
| View manager `focusChanged$`  | Another pane got the focus             | `paneId`, or `null` when none has it   |
| View manager `documentMoved$` | A document moved between panes         | `documentId`, `fromPaneId`, `toPaneId` |

The view manager's events are streams on `inject(EpdfViewManager)`. Opening and closing documents are on
[Opening documents](opening#events).

## Good to know

### Memory

Each open document holds its file in the engine's memory. Close documents the user is done with,
especially large ones.

### One Stage per view

A Stage shows one document. For two documents at once, render two Stages, each in its own
`[epdfDocumentScope]`. For two views of one document, see
[Two views of one document](../viewing/stage#two-views-of-one-document).
