---
title: "Angular SeatManager"
description: "Embed the live SeatManager control room in an Angular app with @seatlayer/angular/manager. Inputs, live updates, outputs, the template ref, and the token rules."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.seatlayer.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Angular SeatManager

`SeatLayerSeatManagerComponent` from `@seatlayer/angular/manager` puts the live
control room inside your Angular app. It is a standalone component over the
[`SeatManager` class](/platform/seat-manager) in `@seatlayer/js/manager`: the
same board, the same tools, the same token rules. This page covers what is
different in Angular. Read the [control-room guide](/platform/embedded-control-room)
for the board itself.

Available from `@seatlayer/angular` 0.106.0. Angular 17 or newer.

## Import it from the manager entry

```ts
import { SeatLayerSeatManagerComponent } from "@seatlayer/angular/manager";
```

The component is not exported from the package root. `/manager` is a separate
entry point that holds the control room and nothing else, so a buyer page that
imports `@seatlayer/angular` stays the same size, and a control room does not
carry the buyer picker.

## Mount the board

Your backend mints a short-lived, event-scoped `mse_` token after it checks
that the signed-in user may manage the event. Never put an `sk_` secret key in
browser code. Keep the token in memory, never in a URL, browser storage, or
logs.

```ts
import { Component, Input } from "@angular/core";
import {
  SeatLayerSeatManagerComponent,
  type SeatManagerMode,
  type SeatManagerOpenOrder,
  type SeatManagerRoomState,
} from "@seatlayer/angular/manager";

@Component({
  selector: "app-control-room",
  standalone: true,
  imports: [SeatLayerSeatManagerComponent],
  template: `
    <seatlayer-seat-manager
      #room
      eventKey="<YOUR_EVENT_KEY>"
      [token]="session.token"
      [tokenExpiresAt]="session.expiresAt"
      [onTokenRefresh]="mintManageSession"
      [(mode)]="mode"
      (roomStateChange)="onRoomState($event)"
      (openOrder)="openOrder($event)"
      (errored)="showOperatorError($event)"
      style="height: calc(100vh - 96px)"
    />
    <button (click)="room.showObjects(['A-2', 'A-3'], 'Resale')">Show resale seats</button>
  `,
})
export class ControlRoomComponent {
  @Input({ required: true }) session!: { token: `mse_${string}`; expiresAt: number };
  mode: SeatManagerMode = "view";

  // An arrow function, so the board can call it without losing `this`.
  mintManageSession = async () => {
    const next = await fetch("/api/events/<YOUR_EVENT_KEY>/manage-session", {
      method: "POST",
    }).then((response) => response.json());
    return { token: next.token, expiresAt: next.expiresAt };
  };

  onRoomState(state: SeatManagerRoomState) {
    console.log("room is", state);
  }

  openOrder(order: SeatManagerOpenOrder) {
    // Route to your own order page.
  }

  showOperatorError(error: unknown) {
    console.error(error);
  }
}
```

The element is `display: block`; give it a real height and the board fills
it. The board is destroyed when Angular destroys the component.

## Inputs

The inputs are the [constructor options](/platform/seat-manager#constructor-options)
without `container`. **Live** inputs change the running board in place: the
camera, selection, live connection and DOM stay. **Read once** inputs are read
when the board is built. Changing `eventKey` or `apiBase` builds a new board.

| Input | When it applies |
|---|---|
| `eventKey`, `apiBase` | Changing either builds a new board |
| `token`, `tokenExpiresAt` | Live |
| `onTokenRefresh` | Live. A function input that returns the new token |
| `capabilities` | Live |
| `currency` | Live |
| `theme` | Live |
| `themeMode` | Applied before the first paint, then live |
| `keepLiveWhileHidden` | Live |
| `mode` | Live. Use `[(mode)]` to follow the operator's tool |
| `followLive` | Live. Use `[(followLive)]` to follow the operator's choice |
| `selectableObjects`, `unavailableObjectsSelectable` | Live |
| `unavailableObjects`, `unavailableObjectsReason` | Live |
| `categoryPrices` | Live |
| `maxSelectedObjects`, `numberOfPlacesToSelect` | Live |
| `isObjectSelectable` | Live |
| `sale` | Live, compared by value |
| `mapInsets` | Live, compared by value |
| `tools`, `chrome` | Read once |
| `selectedObjects` | Read once |
| `kpis` | Read once. `false` when your own header shows the numbers |
| `timeZone` | Read once |
| `organizationName` | Read once |
| `colourBy` | Read once |
| `focusSection`, `focusSeat`, `focusSeats`, `focusCategory` | Read once |
| `arrivedFrom` | Read once. Its `back.go()` runs inside the Angular zone |

"Compared by value" means a new object with the same fields does nothing, so a
template can build `sale` or `mapInsets` inline without repainting the board on
every change detection.

## Outputs

| Output | Payload |
|---|---|
| `(ready)` | none |
| `(tallies)` | `SeatManagerTallies` |
| `(activity)` | `SeatManagerActivity` |
| `(controlRoom)` | `ControlRoomSnapshot` |
| `(modeChange)` | `SeatManagerMode` |
| `(followLiveChange)` | `boolean` |
| `(selectionChange)` | `ExpandedSeat[]` |
| `(objectSelected)`, `(objectDeselected)` | `ExpandedSeat` |
| `(selectionValidityChange)`, `(selectionValid)`, `(selectionInvalid)` | `SeatManagerSelectionValidity` |
| `(selectionLimit)` | `number` |
| `(filteredSectionChange)` | `SeatManagerFilteredSection[]` |
| `(areaClick)` | `SeatManagerArea` |
| `(actionComplete)` | `SeatManagerActionResult` |
| `(connectionChange)` | `SeatManagerConnection` |
| `(roomStateChange)` | `SeatManagerRoomState`: `connecting`, `live`, `paused`, `offline` or `ended` |
| `(errored)` | `unknown` |
| `(openOrder)` | `SeatManagerOpenOrder`: `{ id, displayRef }` |
| `(openTrend)` | `SeatManagerOpenTrend`: `{ kpi, sectionId? }` |
| `(openDesigner)` | none. Bind it and Sections on a chart without sections offers "Open the designer" |

The board offers "Open order" and the trend link only when you bind
`(openOrder)` or `(openTrend)`, so it never shows a link that goes nowhere.
The board checks this when it is built.

`(errored)` rather than `(error)`, as on the seating chart: `error` collides
with the native DOM error event on the host element.

## Zone behaviour

The board runs a render loop, timers and a live connection. They run outside
the Angular zone, so they do not schedule change detection for your whole app.
Each output, `onTokenRefresh` and `arrivedFrom.back.go()` re-enter the zone, so
your handlers, `HttpClient` calls and router navigation behave as usual.

## Template ref

A template ref (`#room`) has the same methods as the React handle:

| Method | Result |
|---|---|
| `setMode(mode)` | Switch tool |
| `setToken(token, expiresAt?)` | Rotate the token without a rebuild |
| `setHeatOverlay(enabled)`, `setFollowLive(enabled)` | Toggle the overlay or camera following |
| `setTrendWindow(minutes)` | Refresh and return the control-room snapshot |
| `enterFullscreen()`, `exitFullscreen()`, `isFullscreen()` | Fullscreen |
| `block(labels?, { releaseAt?, reason? })`, `unblock(labels?)`, `unblockAll()` | Block or unblock inventory |
| `cancelBooking(labels, bookingRef)` | Cancel by booking reference |
| `setCategory(categoryKey, labels?)`, `setTableBooking(tableIds, mode, bounds?)` | Event-only category and table changes |
| `selectAll()`, `selectSection(id)`, `selectByLabels(labels)` | Select seats |
| `selectObjects(labels)`, `deselectObjects(labels)` | Custom selection by label |
| `selectCategories(keys)`, `deselectCategories(keys)` | Custom selection by category |
| `setSelectableObjects`, `setUnavailableObjectsSelectable`, `setUnavailableObjects`, `setCategoryPrices`, `setObjectSelectable`, `setMaxSelectedObjects`, `setNumberOfPlacesToSelect` | The live inputs, called directly |
| `getSelectionValidity()` | Exact-count state, or `null` |
| `setFilteredSection(label)`, `clearFilteredSection()`, `getFilteredSections()` | Section filter |
| `focusSection(label)` | Frame matching sections without filtering |
| `clearSelection()`, `getSelection()` | Selection |
| `getReport()`, `getControlRoomSnapshot(minutes?)`, `getLog({ limit?, before? })` | Reports and audit log |
| `setHoldTtl(msOrNull)` | Set or clear the event checkout window |
| `getConnection()` | Live link state, or `null` before the board exists |
| `zoomToFit()` | Fit the chart |
| `showObjects(labels, name?)` | Light and frame these seats, named in the legend. `null` clears |

The `[focusSection]` input opens the board on a section. The `focusSection()`
method frames a section later. In code, the input's class field is
`focusSectionOnOpen`, because a class cannot have a field and a method with
the same name.

A method called before the board exists returns an empty answer.
`getReport`, `getControlRoomSnapshot` and `setTrendWindow` reject with
`not ready`.

## Types

`@seatlayer/angular/manager` exports `SeatManagerOpenOrder`,
`SeatManagerOpenTrend`, `SeatManagerOptions`, `SeatManagerMode`,
`EventScopedManageToken`, `SeatManagerTallies`, `SeatManagerSale`,
`SeatManagerActivity`, `SeatManagerActionResult`, `SeatManagerConnection`,
`SeatManagerRoomState`, `SeatManagerFilteredSection`,
`SeatManagerSelectionValidity`, `SeatManagerCategoryPrice`,
`EventTableBookingMode`, `ControlRoomActivityEntry`,
`ControlRoomSectionMetric` and `ControlRoomSnapshot`.

Continue to the [SeatManager reference](/platform/seat-manager), the
[control-room guide](/platform/embedded-control-room), and
[embed sessions](/platform/embed-sessions).

Source: https://docs.seatlayer.io/platform/angular-seat-manager/index.mdx
