@fsegurai/ngx-codemirror

中文文档
22.0.0发布于 2026/10/15MITTypeScriptES Module近 30 天下载 5513

Angular library that uses codemirror to create a code editor

原始使用文档
文档目录18 节
  1. Table of contents
  2. Installation
  3. Compatibility
  4. Quick start
  5. Configuration
  6. Themes
  7. Extensions
  8. Convenience inputs
  9. Errors
  10. Programmatic API
  11. Components API
  12. Shared inputs
  13. ngx-code-editor
  14. ngx-code-diff-editor
  15. ngx-code-unified-diff-editor
  16. Migrating from 21.x
  17. Demo application
  18. License

NGX Codemirror Logo

Build Main Status Latest Release
GitHub contributors Dependency status for repo GitHub License
Stars Forks NPM Downloads

@fsegurai/ngx-codemirror brings CodeMirror 6 to Angular as three standalone, signal-based components:

  • ngx-code-editor - a code editor.
  • ngx-code-diff-editor - a side-by-side diff editor (MergeView).
  • ngx-code-unified-diff-editor - a unified diff editor (unifiedMergeView).

They work with Signal Forms, reactive forms and ngModel, and are zoneless and SSR-safe. Themes and extensions can be registered once with provideCodeEditor() and loaded lazily.

Table of contents

Installation

Install the library with its CodeMirror peer dependencies:

npm install @fsegurai/ngx-codemirror codemirror @codemirror/autocomplete @codemirror/commands @codemirror/language @codemirror/lint @codemirror/merge @codemirror/search @codemirror/state @codemirror/view

@angular/common, @angular/core and @angular/forms (^22) are peer dependencies too. For syntax highlighting, add the language descriptions you need, for example all of them with:

npm install @codemirror/language-data

Compatibility

Angular@fsegurai/ngx-codemirror
22.x22.x
21.x21.x
20.x20.x
19.x19.x

Since 21.x the library is zoneless (provideZonelessChangeDetection()) and does not require Zone.js.

Quick start

Import the standalone component and bind its value with the forms API you use:

import { Component, signal } from '@angular/core';
import { FormField, form } from '@angular/forms/signals';
import { languages } from '@codemirror/language-data';
import { CodeEditorComponent } from '@fsegurai/ngx-codemirror';

@Component({
  selector: 'app-root',
  imports: [CodeEditorComponent, FormField],
  template: `
    <ngx-code-editor
      [formField]="editorForm.code"
      language="javascript"
      [languages]="languages"
      theme="dark"
      [lineWrapping]="true" />
  `,
})
export class AppComponent {
  protected readonly languages = languages;
  protected readonly model = signal({ code: 'const answer = 42;' });
  protected readonly editorForm = form(this.model);
}

The same component works with every forms API:

BindingImport
<ngx-code-editor [formField]="f.code" />FormField from @angular/forms/signals
<ngx-code-editor [formControl]="code" /> / formControlNameReactiveFormsModule
<ngx-code-editor [(ngModel)]="code" />FormsModule
<ngx-code-editor [(value)]="code" />nothing (a plain model())

language is looked up by name or alias in languages: pass both, or set languages once in provideCodeEditor(). Without any languages, the editor emits a language-not-found error.

Configuration

provideCodeEditor(options, ...features) sets the default inputs of every editor of an injector. Use it in the application providers, a route's providers or a component's providers; a nested call merges over its ancestors.

import { ApplicationConfig } from '@angular/core';
import { languages } from '@codemirror/language-data';
import { provideCodeEditor, withExtensionPresets, withExtensions, withThemes } from '@fsegurai/ngx-codemirror';

export const appConfig: ApplicationConfig = {
  providers: [
    provideCodeEditor(
      { theme: 'auto', setup: 'basic', lineWrapping: true, languages },
      withThemes(
        { nord: () => import('@fsegurai/codemirror-theme-bundle').then((m) => m.nord) },
        { light: 'light', dark: 'nord' },
      ),
      withExtensions([myExtension]),
      withExtensionPresets({ vim: () => import('@replit/codemirror-vim').then((m) => m.vim()) }),
    ),
  ],
};
FeatureWhat it does
withThemes(themes, { light?, dark? })Registers named themes, eager or lazy. light / dark are the themes 'auto' uses.
withExtensions(extensions, { mode? })Global extensions for every editor. See Extensions.
withExtensionPresets(presets)Named extensions, enabled per editor with [presets]="['vim']".

Precedence, lowest first: built-in defaults, providers (a nested one over its parent), instance inputs. An undefined value never overrides. The options are those of the inputs (theme, setup, placeholder, indentWithTab, indentUnit, lineWrapping, highlightWhitespace, languages, the convenience inputs and presets), plus global extensions.

Themes

The theme input (and the theme option) accepts:

ValueTheme
'light'CodeMirror's default look (the built-in default)
'dark'A built-in dark base
'auto'Follows prefers-color-scheme, using the light / dark names of withThemes() (else the built-ins)
a registered nameA theme registered with withThemes(); a lazy theme is loaded on first use and cached
an ExtensionAny CodeMirror theme

To switch every editor at once (for example from an app light/dark toggle), use CodeEditorThemeService. Editors with their own theme input keep it.

inject(CodeEditorThemeService).set('nord'); // undefined goes back to the provider's theme

Precedence, highest first: the instance theme input, CodeEditorThemeService, the theme of provideCodeEditor(), 'light'. An unknown name emits invalid-theme and keeps the current theme.

Extensions

Extensions can be eager (Extension) or lazy (() => Promise<Extension>, loaded once and shared by every editor).

WhereHowNotes
Every editorwithExtensions([...]) or provideCodeEditor({ extensions })Nested providers append to their parent.
One editor[extensions]="[...]"Appended after the global extensions.
Replace globalswithExtensions([...], { mode: 'replace' })An editor with its own extensions input uses only those.
Opt-in per editorwithExtensionPresets({ name: ext }) + [presets]="['name']"Unknown names emit unknown-preset.
withExtensions([
  EditorView.lineWrapping,
  () => import('@replit/codemirror-indentation-markers').then((m) => m.indentationMarkers()),
]);

A failed lazy load emits extension-load-failed and keeps the extensions already applied.

Convenience inputs

Common CodeMirror features have their own inputs, so they need no extension code. With setup="basic" the boolean ones default to on; with another setup they default to off. An explicit value always wins.

InputTypeDefault
lineNumbersbooleanon with 'basic'
foldGutterbooleanon with 'basic'
highlightActiveLinebooleanon with 'basic'
searchboolean | SearchConfigon with 'basic'
autocompletionboolean | AutocompletionConfigon with 'basic'
tabSizenumber4
lintLintSource | { source, config?, gutter? } | nullnone (null turns the provider's linter off)
keymapreadonly KeyBinding[]none (high precedence)
<ngx-code-editor [formField]="f.code" [lineNumbers]="false" [tabSize]="2" [lint]="{ source: myLinter, gutter: true }" />

Errors

Every component emits error with a CodeEditorError (discriminated by code) instead of throwing, and keeps its current state. In development mode the error is also logged as a [ngx-codemirror] warning.

codeConstantEmitted when
language-not-foundERROR_LANGUAGE_NOT_FOUNDlanguage is not in languages (available lists the names)
language-load-failedERROR_LANGUAGE_LOAD_FAILEDLoading the language failed (cause)
invalid-themeERROR_INVALID_THEMEUnknown theme name (available)
theme-load-failedERROR_THEME_LOAD_FAILEDLoading a lazy theme failed (cause)
extension-load-failedERROR_EXTENSION_LOAD_FAILEDLoading a lazy extension failed (source, cause)
unknown-presetERROR_UNKNOWN_PRESETUnknown preset name (available)
invalid-indent-unitERROR_INVALID_INDENT_UNITInvalid indentUnit (value); the default is used
onError(error: CodeEditorError): void {
  if (error.code === ERROR_LANGUAGE_NOT_FOUND) console.warn(error.message, error.available);
}

Programmatic API

Get the component with viewChild() and use its signals and methods:

private readonly editor = viewChild.required(CodeEditorComponent);

format(): void {
  const code = this.editor().getValue();
  this.editor().setValue(prettify(code)); // emits valueChange once
  this.editor().focus();
}
MemberEditor / unifiedDiff editor
view() / mergeView()EditorView | undefinedMergeView | undefined
ready outputemits the EditorViewemits the MergeView
focus()focuses the editorfocuses the modified side
getValue() / setValue()string{ original, modified }
goToNextChunk() / goToPreviousChunk()unified onlymoves on the modified side
getChunks()unified onlythe changed chunks

Views are created after the first render (afterNextRender), so view() is undefined on the server and before ready emits.

Components API

Shared inputs

All three components take these inputs, plus the convenience inputs.

InputTypeDescription
disabledbooleanNot editable and read-only.
readonlybooleanRead-only, but can be focused and selected.
invalid / required / touchedbooleanForm state, set by the forms integration (aria-invalid / aria-required).
themeThemeSee Themes.
placeholderstringShown when the document is empty (modified side of a diff editor).
indentWithTabbooleanThe Tab key indents.
indentUnitnumber | stringSpaces (0 = default) or a whitespace string such as '\t'.
lineWrappingbooleanWraps long lines.
highlightWhitespacebooleanHighlights whitespace characters.
languagesreadonly LanguageDescription[]The languages language is looked up in.
languagestringLanguage name or alias (case-insensitive).
setup'basic' | 'minimal' | nullThe built-in setup. Default 'basic'.
presetsreadonly string[]Presets registered with withExtensionPresets().

Shared outputs: touch (on blur, marks a bound field touched) and error (CodeEditorError).

ngx-code-editor

Input / outputTypeDescription
valuemodel<string>The document. Two-way bindable; used by the forms integration.
extensionsExtensionInputExtra extensions, eager or lazy.
rootDocument | ShadowRootThe DOM root (read at creation).
autoFocusbooleanFocuses the editor on creation.
(valueChange)stringA user edit (programmatic writes do not emit).
(focused) / (blurred)voidFocus changes.
(ready)EditorViewThe view was created.

ngx-code-diff-editor

Input / outputTypeDescription
valuemodel<DiffEditorModel>Both documents, { original, modified }. Works with forms.
originalValue / modifiedValuestringOne side; the latest of these and value wins.
originalExtensions / modifiedExtensionsExtensionInputExtra extensions per side.
originalReadonlybooleanMakes only the original side read-only.
orientation'a-b' | 'b-a'Which document is shown first.
revertControls'a-to-b' | 'b-to-a'Revert buttons between changed chunks.
renderRevertControl() => HTMLElementRenders the revert buttons.
highlightChangesbooleanHighlights changed text. Default true.
gutterbooleanGutter markers next to changed lines. Default true.
collapseUnchanged{ margin?, minSize? }Collapses unchanged text.
diffConfigDiffConfigOptions of the diff algorithm.
(valueChange)DiffEditorModelA user edit on either side.
(originalValueChange) / (modifiedValueChange)stringA user edit on one side.
(originalFocused) / (originalBlurred)voidFocus of the original side.
(modifiedFocused) / (modifiedBlurred)voidFocus of the modified side.
(ready)MergeViewThe merge view was created.

ngx-code-unified-diff-editor

It takes the inputs and outputs of ngx-code-editor (value is the modified document), plus:

Input / outputTypeDescription
originalmodel<string>The document compared with. Accepting a chunk updates it ([(original)]).
mergeControlsboolean | (type, action) => HTMLElementAccept/reject buttons. Default true.
highlightChangesbooleanHighlights changed text. Default true.
gutterbooleanGutter markers next to changed lines. Default true.
syntaxHighlightDeletionsbooleanSyntax-highlights deleted lines. Default true.
allowInlineDiffsbooleanShows small changes inline. Default false.
collapseUnchanged{ margin?, minSize? }Collapses unchanged text.
diffConfigDiffConfigOptions of the diff algorithm.
<ngx-code-unified-diff-editor [(original)]="original" [(value)]="modified" language="typescript" [languages]="languages" />

Migrating from 21.x

22.0.0 contains breaking changes. Check each item; the CHANGELOG lists them all.

  1. Minimum versions: Angular ^22 (including @angular/forms), TypeScript 6.0, Node.js >=24.15 for the workspace tooling.

  2. New peer dependencies: install @codemirror/autocomplete, @codemirror/commands, @codemirror/language, @codemirror/lint, @codemirror/merge, @codemirror/search, @codemirror/state, @codemirror/view and codemirror (see Installation). @codemirror/language-data is no longer installed by the library.

  3. CodeEditorModule is removed: import CodeEditorComponent, CodeDiffEditorComponent or CodeUnifiedDiffEditorComponent directly.

  4. Forms: the components implement Signal Forms FormValueControl instead of ControlValueAccessor (NG_VALUE_ACCESSOR is gone). [formField], formControlName, [formControl] and [(ngModel)] keep working. Code calling writeValue(), registerOnChange(), registerOnTouched() or setDisabledState() must set value or disabled instead.

  5. Renamed outputs (they clashed with native DOM events):

    Component21.x22.x
    editor(change)(valueChange)
    editor(focus) / (blur)(focused) / (blurred)
    diff editor(originalFocus) / (originalBlur)(originalFocused) / (originalBlurred)
    diff editor(modifiedFocus) / (modifiedBlur)(modifiedFocused) / (modifiedBlurred)
  6. value is a model() and only user edits emit valueChange. disabled is an input: [(disabled)] and (disabledChange) are gone, and a disabled editor is also read-only. focus is now a method.

  7. Errors are emitted, not logged: handle the new error output. An unknown language, a failed language load, an unknown theme name and an invalid indentUnit emit error and keep the current state; console output is a dev-mode warning only.

  8. Inputs default to undefined so provideCodeEditor() can supply them; the effective defaults are unchanged. Code reading the input signals directly now sees undefined when unset.

  9. setup="basic" no longer includes line numbers, fold gutter, active line, search and autocompletion directly: they are the convenience inputs, on by default with 'basic'. The resulting editor is equivalent; set an input to false to remove that part.

  10. Theme accepts 'dark', 'auto' and registered names; an unknown string emits invalid-theme instead of being passed to CodeMirror. indentUnit is number | string.

Demo application

To see the components in action, check out the demo.

To set up the demo locally:

git clone https://github.com/fsegurai/ngx-codemirror.git
cd ngx-codemirror
bun install
bun start

This serves the application at http://localhost:4200.

License

Licensed under MIT.

版本与发布记录

@fsegurai/ngx-codemirror 共发布 13 个版本,首次发布于 2024/12/04;过去 12 个月发布了 3 个版本。

版本发布日期距今
22.0.0latest2026/10/151 个月前
21.0.02026/10/101 个月前
21.0.0-beta.0beta2026/10/081 个月前
20.0.02025/06/251.4 年前
20.0.0-beta.12025/06/181.4 年前
20.0.0-beta.02025/06/181.4 年前

关于 @fsegurai/ngx-codemirror 的常见问题

@fsegurai/ngx-codemirror 是什么?

Angular library that uses codemirror to create a code editor

如何安装 @fsegurai/ngx-codemirror?

在项目目录运行 npm install @fsegurai/ngx-codemirror,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:codemirror(^6.0.2)、@angular/core(^22.0.0)、@angular/forms(^22.0.0)、@angular/common(^22.0.0)、@codemirror/lint(^6.9.7)、@codemirror/view(^6.43.13)、@codemirror/merge(^6.12.2)、@codemirror/state(^6.7.6)、@codemirror/search(^6.7.2)、@codemirror/commands(^6.11.1)、@codemirror/language(^6.12.4)、@codemirror/autocomplete(^6.20.3)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@fsegurai/ngx-codemirror 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@fsegurai/ngx-codemirror 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@fsegurai/ngx-codemirror 可以商用吗?

@fsegurai/ngx-codemirror 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@fsegurai/ngx-codemirror 的下载量有多少?

根据 npm 官方下载统计,@fsegurai/ngx-codemirror 在 2026/10/16 – 2026/11/14 这 30 天内被下载 5,513 次,日均约 184 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@fsegurai/ngx-codemirror 还在维护吗?

最新版本 22.0.0 发布于 2026/10/15(1 个月前);至今共发布 13 个版本,其中过去 12 个月发布了 3 个;首次发布于 2024/12/04。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。