ngx-smart-editor
v0.2.0
Published
Angular rich text editor library with inline variables, JSON document model, Markdown conversion, and extensible token plugins.
Maintainers
Readme
ngx-smart-editor
ngx-smart-editor is an Angular library for inline rich-text editing with dynamic tokens, JSON as the source of truth, and bidirectional JSON ↔ Markdown conversion.
It is designed for use cases such as:
- inline variables inside text
- lightweight Notion-like editing
- plugin-driven custom tokens
- Markdown import/export for business syntax
Features
- Angular standalone component:
ngx-smart-editor ControlValueAccessorsupport for Reactive Forms- canonical JSON document model
- Markdown serialization and parsing helpers
- inline variable picker triggered from
{ - IDE-like inline variable search with keyboard-first selection
- extensible plugin architecture
Installation
Install the package and Angular peer dependencies in your application:
npm install ngx-smart-editorBuild
Build the library package:
npm run build:libThis command synchronizes the root README.md into projects/ngx-smart-editor/README.md before running ng build ngx-smart-editor, so the same README is published to npm inside dist/ngx-smart-editor.
Publish
Check the library version before publishing:
npm run version:check:libUpdate projects/ngx-smart-editor/package.json before every publication.
Then publish the package:
npm run publish:libEquivalent manual commands:
npm run build:lib
cd dist/ngx-smart-editor
npm publishQuick Start
Import the component and helpers from the package:
import { ChangeDetectionStrategy, Component, computed } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import {
SmartEditorComponent,
SmartEditorPickerEmptyDirective,
SmartEditorPickerFooterDirective,
SmartEditorPickerHeaderDirective,
SmartEditorPickerOptionDirective,
defaultEditorPlugins,
parseMarkdown,
serializeMarkdown,
validateDocument,
} from 'ngx-smart-editor';
import type { EditorDocument, VariableOption } from 'ngx-smart-editor';
@Component({
selector: 'app-editor-demo',
imports: [
ReactiveFormsModule,
SmartEditorComponent,
SmartEditorPickerHeaderDirective,
SmartEditorPickerOptionDirective,
SmartEditorPickerEmptyDirective,
SmartEditorPickerFooterDirective,
],
template: `
<ngx-smart-editor
[formControl]="editorControl"
[plugins]="plugins"
[variableOptions]="variableOptions"
placeholder="Write text and press { to insert a variable"
ariaLabel="Smart editor"
>
<ng-template smartEditorPickerHeader let-query="query">
<div>
<strong>Custom picker</strong>
<p>{{ query ? 'Searching: ' + query : 'Keep typing in the editor to filter variables.' }}</p>
</div>
</ng-template>
<ng-template smartEditorPickerOption let-option let-active="active">
<div [attr.data-active]="active">
<strong>{{ option.label }}</strong>
<small>{{ option.key }}</small>
</div>
</ng-template>
<ng-template smartEditorPickerEmpty let-query="query">
<p>No result for "{{ query }}"</p>
</ng-template>
<ng-template smartEditorPickerFooter>
<button type="button">Manage variables</button>
</ng-template>
</ngx-smart-editor>
<pre>{{ markdown() }}</pre>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EditorDemoComponent {
readonly plugins = defaultEditorPlugins;
readonly variableOptions: VariableOption[] = [
{ key: 'customer.firstName', label: 'Customer first name' },
{ key: 'customer.lastName', label: 'Customer last name' },
{ key: 'ticket.id', label: 'Ticket ID' },
{ key: 'ticket.status', label: 'Ticket status' },
];
readonly editorControl = new FormControl<EditorDocument>(
[
{ type: 'text', content: 'Hello ' },
{ type: 'variable', key: 'customer.firstName', label: 'Customer first name' },
{ type: 'text', content: ', your ticket is ' },
{ type: 'variable', key: 'ticket.id', label: 'Ticket ID' },
{ type: 'text', content: '.' },
],
{ nonNullable: true },
);
readonly markdown = computed(() => serializeMarkdown(this.editorControl.getRawValue(), this.plugins));
importMarkdown(markdown: string): void {
this.editorControl.setValue(parseMarkdown(markdown, this.plugins));
}
validate(): void {
console.log(validateDocument(this.editorControl.getRawValue()));
}
}Plain / Unstyled Usage
By default, ngx-smart-editor behaves like a plain contenteditable inside your own layout. It does not render the Add variable toolbar and does not add a frame, background, border, or padding:
<ngx-smart-editor
[formControl]="message"
[variableOptions]="variables"
placeholder="Type { to insert a variable"
/>The autocomplete picker still opens when the user types {.
Style it from your application with a regular class:
<ngx-smart-editor
class="message-editor"
[formControl]="message"
[variableOptions]="variables"
/>.message-editor {
display: block;
min-height: 7rem;
padding: 0.75rem;
border: 1px solid #d0d7de;
border-radius: 0.5rem;
font: 400 1rem/1.5 system-ui, sans-serif;
--ngx-smart-editor-token-background: #e7f5ff;
--ngx-smart-editor-token-color: #075985;
--ngx-smart-editor-token-padding: 0.1rem 0.35rem;
--ngx-smart-editor-token-radius: 0.25rem;
}If you want the built-in demo styling, opt in explicitly:
<ngx-smart-editor
[formControl]="message"
[variableOptions]="variables"
[showToolbar]="true"
[unstyled]="false"
/>Customize the Autocomplete Dropdown
The variable autocomplete dropdown can be customized with projected templates. Import only the directives you use:
import {
SmartEditorComponent,
SmartEditorPickerEmptyDirective,
SmartEditorPickerFooterDirective,
SmartEditorPickerHeaderDirective,
SmartEditorPickerOptionDirective,
} from 'ngx-smart-editor';<ngx-smart-editor
[formControl]="message"
[variableOptions]="variables"
>
<ng-template smartEditorPickerHeader let-query="query">
<div class="variable-menu-header">
<strong>Variables</strong>
<span>{{ query || 'Type to filter' }}</span>
</div>
</ng-template>
<ng-template smartEditorPickerOption let-option let-active="active">
<div class="variable-menu-option" [class.active]="active">
<strong>{{ option.label }}</strong>
<small>{{ option.key }}</small>
</div>
</ng-template>
<ng-template smartEditorPickerEmpty let-query="query">
<p>No variable matches "{{ query }}".</p>
</ng-template>
<ng-template smartEditorPickerFooter>
<button type="button">Manage variables</button>
</ng-template>
</ngx-smart-editor>Available picker templates:
smartEditorPickerHeadersmartEditorPickerOptionsmartEditorPickerEmptysmartEditorPickerLoadingsmartEditorPickerErrorsmartEditorPickerFooter
The dropdown panel also reads CSS variables from the ngx-smart-editor host:
.message-editor {
--ngx-smart-editor-picker-background: #fbfeff;
--ngx-smart-editor-picker-border: 1px solid #9bc7d5;
--ngx-smart-editor-picker-radius: 0.55rem;
--ngx-smart-editor-picker-shadow: 0 16px 34px rgba(15, 97, 115, 0.2);
--ngx-smart-editor-picker-header-background: #e7f7fb;
--ngx-smart-editor-picker-option-radius: 0.4rem;
--ngx-smart-editor-picker-option-active-background: #dff3f8;
--ngx-smart-editor-picker-option-active-border-color: #72b7c8;
--ngx-smart-editor-picker-footer-background: #f3fbfd;
}Document Model
The editor works with a canonical JSON document:
type EditorDocument = EditorNode[];
type EditorNode = TextNode | VariableNode | DropdownNode | CustomNode;
interface TextNode {
type: 'text';
content: string;
}
interface VariableNode {
type: 'variable';
key: string;
label?: string;
}
interface DropdownNode {
type: 'dropdown';
key: string;
value?: string;
options: string[];
}JSON is the source of truth. Markdown is an exchange format.
Markdown Helpers
The library exposes pure helpers for conversion and validation:
import {
normalizeDocument,
parseMarkdown,
serializeMarkdown,
validateDocument,
} from 'ngx-smart-editor';Typical usage:
const markdown = serializeMarkdown(document, defaultEditorPlugins);
const parsed = parseMarkdown(markdown, defaultEditorPlugins);
const normalized = normalizeDocument(parsed);
const validation = validateDocument(normalized);Variable Markdown Syntax
{{ variable:customer.firstName | label:Customer first name }}Dropdown Markdown Syntax
{{ dropdown:status | value:in-progress | options:todo,in-progress,done }}Invalid business tokens are preserved as plain text instead of crashing the parser.
Component Inputs and Output
ngx-smart-editor exposes these main bindings:
[plugins]: custom plugin list merged with the default plugins[variableOptions]: list used by the inline variable picker[placeholder]: placeholder text when the document is empty[ariaLabel]: accessibility label for the editor surface[showToolbar]: defaults tofalse; set totrueto render the built-in toolbar[unstyled]: defaults totrue; set tofalseto enable the built-in frame styling(documentChange): emits the normalizedEditorDocument
Because the component implements ControlValueAccessor, you can use:
[formControl]formControlName[(ngModel)]if needed in your application
Keyboard Behavior
Current inline interaction includes:
- type normally in text segments
- press
{to open the variable picker - keep typing in the editor to filter variables by label or key
- use
Backspacein the editor to edit or clear the query - press
ArrowDown/ArrowUpto move through matches - press
Enterto insert the active variable - press
Backspaceon an adjacent text boundary to remove an inline token - click an existing variable token to reopen the picker and replace it
