Getting Started
Overview
Installation
Theme
Forms
Basic Inputs
Custom Inputs
Select
Components
Components
78
Micro Charts
Navigation
Libraries
Content Editor
Data View
Form Builder
Headless Editor
Image Designer
Kanban Board
PDF Builder
PDF Signer
PDF Viewer
Video Player
GitHub repositoryGitHub starsGitHub forksNgStarter UI npm versionNgStarter UI npm downloads per month

Document Model

An editor document is plain, versioned JSON. It is the only source of truth: the DOM is rendered from it, and browser input is translated into changes of it. You can store it, send it over the network, diff it, and render it anywhere.

Loading example…

Structure

const document: NgsHeadlessEditorDocument = {
  version: 1,
  blocks: [
    {
      id: 'paragraph-1',
      type: 'paragraph',
      content: [
        { type: 'text', text: 'Hello ', marks: [] },
        { type: 'text', text: 'world', marks: [{ type: 'bold' }] }
      ]
    },
    {
      id: 'callout-1',
      type: 'callout',
      content: null,                          // atomic block
      attrs: { tone: 'info', text: 'Heads up' }
    }
  ]
};

const selection: NgsHeadlessEditorSelection = {
  anchor: { blockId: 'paragraph-1', offset: 6 },
  focus: { blockId: 'paragraph-1', offset: 11 }  // selects "world"
};
TypeDescription
NgsHeadlessEditorDocumentRoot object with version: 1 and an ordered, non-empty list of blocks.
NgsHeadlessEditorBlockAddressable unit with a stable id, a type registered by a plugin, content, and optional attrs.
NgsHeadlessEditorTextA text run inside a text block: a string and the marks applied to all of it.
NgsHeadlessEditorMarkInline annotation with a type and optional serializable attrs (string, number, boolean, or null values).
NgsHeadlessEditorPointA position: block id plus a text offset inside that block.
NgsHeadlessEditorSelectionAn anchor and a focus point. The focus can be before the anchor for backward selections.

Text blocks and atomic blocks

A block whose content is an array of text runs is a text block: the caret can move through it and the user can type into it. Any other content, commonly null with data in attrs, makes an atomic block such as a divider, an image, or an Angular component. Atomic blocks are selected and deleted as a whole. Each text block is one line of the document; lists, quotes, and code are sequences of blocks of the same type. See Blocks.

Normalization

Every document that enters the editor is normalized. After normalization:

  • The document has at least one block; an empty document becomes a single empty paragraph.
  • Block ids are present and unique; missing or duplicate ids are regenerated.
  • Text runs are never empty, except a single empty run that represents an empty block.
  • Adjacent runs with equal marks are merged.
  • Marks are unique by type and sorted by type, so equal formatting always has one representation.

setDocument() copies its input before normalizing, so later mutations of your object do not leak into the editor.

Immutability and identity

Documents, blocks, runs, and selections are never mutated. Every change produces a new document that reuses the unchanged blocks. The surface relies on this identity to re-render only changed blocks, and the history stores snapshots that share structure instead of copying the document.

const before = editor.document();
editor.insertText('!');               // edits the block that holds the caret
const after = editor.document();

before === after;                      // false: a new document
before.blocks[1] === after.blocks[1];  // true: untouched blocks are shared

Never mutate a document you got from the editor. Create a new object, or use updateBlock(), insertBlock(), and the other editing methods.

Offsets

Offsets count UTF-16 code units, like JavaScript string indexes, and are clamped to the block text length. Deleting a character removes a whole user-perceived character, so emoji with skin tones, family sequences, and flags are removed in one step.

Helpers

import {
  cloneNgsHeadlessEditorDocument,      // deep copy for external mutation
  createNgsHeadlessEditorDocument,     // document with one paragraph
  createNgsHeadlessEditorId,           // unique id with a prefix
  createNgsHeadlessEditorParagraph,    // paragraph block
  createNgsHeadlessEditorText,         // normalized text run
  getNgsHeadlessEditorBlockText,       // text of one block
  getNgsHeadlessEditorDocumentText,    // blocks joined with newlines
  isNgsHeadlessEditorDocumentEmpty,    // true when only whitespace
  isNgsHeadlessEditorTextContent,      // type guard for text blocks
  ngsHeadlessEditorBlocksEqual,
  ngsHeadlessEditorDocumentsEqual,
  ngsHeadlessEditorMarksEqual,
  ngsHeadlessEditorValuesEqual,        // JSON-like deep equality
  normalizeNgsHeadlessEditorDocument,
  normalizeNgsHeadlessEditorMarks,
  normalizeNgsHeadlessEditorTextContent
} from '@ngstarter-ui/components/headless-editor';

const document = createNgsHeadlessEditorDocument('First line');
const text = getNgsHeadlessEditorDocumentText(document);         // 'First line'
const same = ngsHeadlessEditorDocumentsEqual(document, cloneNgsHeadlessEditorDocument(document)); // true

Equality helpers compare structure, ignore object key order, and treat keys with undefined values as absent. They short-circuit on shared blocks, which makes comparing two revisions of a large document cheap.