Angular Markdown Editor: ngx-markdown Setup Guide

September 11, 2026 · 9 min read

Angular Markdown Editor: Build One with ngx-markdown

An Angular Markdown editor built with ngx-markdown is a textarea bound to a signal plus a <markdown> component rendering the preview. That's about 40 lines of Angular, no editor package required. This tutorial builds it with ngx-markdown 22 and standalone components, untangles the three packages that share the name, and covers sanitization, Prism highlighting, and SSR.

Which ngx-markdown Package Do You Mean?

Three different projects answer to nearly the same name, and picking the wrong one costs an afternoon. Searching for ngx markdown editor without the hyphen lands on the same three. Here's the state of each on the npm registry as of 11 September 2026.

NameWhat it isLatest releaseStatus
ngx-markdown (jfcere)Renderer: marked plus Prism, KaTeX, Mermaid, clipboard22.0.2, 29 August 2026Active; version tracks the Angular major
ngx-markdown-editor (lon-yang)Editor built on the Ace code editor5.3.4, 19 October 2023No release in almost three years
@mdefy/ngx-markdown-editor (mdefy)WYSIWYG-style editor built on CodeMirror, uses ngx-markdown for preview11.1.0, 21 January 2021Pinned to Angular 11

The first one is a renderer, not an editor. That's the good news. An Angular markdown editor is a renderer plus a textarea, and you can build the editor half yourself faster than you can configure either packaged editor. Everything below uses jfcere's ngx-markdown.

If you searched for an angularjs markdown editor, or for angular markdown-editor with a hyphen, you're looking at packages from the AngularJS 1.x era. AngularJS support ended at the close of 2021. The path forward is the same component below.

How Do You Build a Two-Pane Angular Markdown Editor?

The build has two steps: install the renderer and register its provider, then write one component that holds the text in a signal and renders it.

Install ngx-markdown and marked

ngx-markdown's major version matches your Angular major. Angular 22 pairs with ngx-markdown 22, whose peer dependencies accept marked 17 or 18. The README's install command is the following.

npm install ngx-markdown marked@^18.0.0 --save

Register the provider in app.config.ts. This is the standalone path the README documents. MarkdownModule.forRoot() still exists for NgModule apps, but the type definitions mark it deprecated and say it will be removed in the next major version.

import { ApplicationConfig } from '@angular/core';
import { provideMarkdown } from 'ngx-markdown';

export const appConfig: ApplicationConfig = {
  providers: [provideMarkdown()],
};

If you plan to load .md files by URL with the [src] input, add provideHttpClient() and pass { loader: HttpClient } to provideMarkdown. For content bound from a variable, the bare call is enough. The ngx-markdown README documents both forms, and its demo on StackBlitz is a quick way to confirm your Angular major is supported.

Write the editor component

The component holds the text in a signal, updates it on every input event, and hands it to <markdown [data]>. ngx-markdown re-parses whenever the bound value changes, so there's no manual refresh.

import { Component, signal } from '@angular/core';
import { MarkdownComponent } from 'ngx-markdown';

@Component({
  selector: 'app-md-editor',
  imports: [MarkdownComponent],
  template: `
    <div class="editor">
      <textarea
        [value]="text()"
        (input)="onInput($event)"
        spellcheck="false"></textarea>
      <markdown class="preview" [data]="text()"></markdown>
    </div>
  `,
  styles: `
    .editor { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; height: 70vh; }
    textarea { font: 14px/1.5 monospace; padding: 1rem; resize: none; }
    .preview { overflow: auto; padding: 1rem; border: 1px solid #ddd; }
  `,
})
export class MdEditorComponent {
  text = signal(`# Release notes

Version **2.1** ships two changes:

- Faster export
- A new [changelog page](https://example.com/changelog)
`);

  onInput(event: Event) {
    this.text.set((event.target as HTMLTextAreaElement).value);
  }
}

Two details worth knowing. MarkdownComponent is standalone, so it goes in the component's imports array with no module. And the textarea uses [value] plus (input) rather than [(ngModel)], which avoids importing FormsModule. If you already use template-driven forms, [(ngModel)] bound to the signal works too in current Angular versions.

Here's the same sample in our editor so you can compare what marked produces with what your component shows.

Release notes

Version 2.1 ships two changes:

Area Change
Export 3x faster
Docs New changelog
  • Tests pass
  • Update screenshots
43 words244 characters14 lines
Markdown

marked handles the GFM table and task list above out of the box, so your preview should match. For the parser details, our Markdown to HTML guide covers what marked does with each element; one sentence is all this post needs.

Sanitization: SecurityContext.HTML vs NONE

ngx-markdown sanitizes by default. The README states that sanitization uses Angular's DomSanitizer with SecurityContext.HTML to prevent XSS. That means inline <script> tags, onerror handlers, and javascript: URLs in user Markdown are stripped before the HTML reaches the DOM.

You can turn it off, and you'll see people do it to allow raw HTML in docs.

import { SecurityContext } from '@angular/core';
import { provideMarkdown, SANITIZE } from 'ngx-markdown';

provideMarkdown({
  sanitize: { provide: SANITIZE, useValue: SecurityContext.NONE },
});

Don't do that for user-generated content. If Angular's sanitizer is too strict for your docs, the README's recommended alternative is to provide a function that runs DOMPurify. That lets you allow specific tags while still blocking scripts.

import DOMPurify from 'dompurify';

provideMarkdown({
  sanitize: { provide: SANITIZE, useValue: (html: string) => DOMPurify.sanitize(html) },
});

The [disableSanitizer]="true" input does the same per component. We prefer keeping the default on and switching to DOMPurify only when a real document needs <details> or <kbd>.

Add Prism Highlighting, KaTeX, and Mermaid

Syntax highlighting, math, and diagrams are optional plugins loaded through angular.json. The Prism setup is three lines: a theme in styles, the core in scripts, and one component file per language you need.

"styles": [
  "src/styles.css",
  "node_modules/prismjs/themes/prism-okaidia.css"
],
"scripts": [
  "node_modules/prismjs/prism.js",
  "node_modules/prismjs/components/prism-typescript.min.js",
  "node_modules/prismjs/components/prism-bash.min.js"
]

Install prismjs@^1.30.0 first. Fenced blocks tagged ts or bash then get colour; untagged blocks stay plain. The code block languages guide lists the identifiers Prism recognises.

Math needs katex@^0.16.0 and marked-katex-extension@^5.0.0, plus node_modules/katex/dist/katex.min.css in styles. You enable it per component with the katex attribute: <markdown katex [data]="text()">. Diagrams need mermaid@^11.0.0, its dist/mermaid.min.js in scripts, and the mermaid attribute. Our equation and Mermaid guides show the syntax each plugin renders.

SSR, Lazy Loading, and Testing

Angular's server-side rendering runs your component on Node, where Mermaid and the clipboard plugin have no window to draw into. In our experience the renderer itself works on the server (marked is plain JavaScript), but the plugins don't. Keep the mermaid and clipboard attributes off components that render during SSR, or guard them with isPlatformBrowser, and let hydration add them on the client.

The editor page rarely needs to be server-rendered anyway. Put it behind a lazy route so the ngx-markdown and marked bundles load only when someone opens the editor.

export const routes: Routes = [
  {
    path: 'editor',
    loadComponent: () => import('./md-editor.component').then(m => m.MdEditorComponent),
  },
];

For unit tests, the component needs the same provider the app does. Add provideMarkdown() to the providers array in TestBed.configureTestingModule. Then set the signal, call fixture.detectChanges(), and assert on the rendered h1 inside the markdown element. One limitation: if the assertion runs before the component has rendered, await fixture.whenStable() before querying the DOM.

When Is a Packaged ngx-markdown Editor Worth It?

The 40-line component has no toolbar, no image upload, and no keyboard shortcuts for bold or lists. If your users are non-technical, those matter, and that's what the two editor packages sell. The trade-off is maintenance. The Ace-based ngx-markdown-editor package hasn't shipped since October 2023 and pulls in Bootstrap and Font Awesome as peer dependencies, and mdefy's CodeMirror editor is pinned to Angular 11.

Our recommendation for a new Angular 22 project is to keep the hand-built component. Add a small toolbar of your own that wraps the selection in ** or - , and put the effort into sanitization and highlighting instead. If you need a Notion-style WYSIWYG experience, look at framework-agnostic editors with Markdown export rather than an Angular-only package. The React and Vue tutorials build the same two-pane editor in those frameworks if you're comparing.

Common ngx-markdown Editor Mistakes

Installing the wrong major. ngx-markdown 22 needs Angular 22. An ERESOLVE peer dependency error from npm almost always means the majors don't match; pin ngx-markdown@<your Angular major>.

Forgetting ngPreserveWhitespaces on static content. Angular strips whitespace from templates by default, which collapses inline Markdown written directly inside <markdown> tags. Bind with [data] (as above) or add the directive.

Disabling the sanitizer to fix a rendering problem. Missing tables or code blocks are a marked or Prism configuration issue, not a sanitizer issue. Turning off SecurityContext.HTML opens an XSS hole and won't fix them.

ngx-markdown Editor FAQ

An Angular Markdown editor in 2026 means ngx-markdown 22 for rendering, a signal-bound textarea for input, and the default sanitizer left on. Add Prism, KaTeX, and Mermaid through angular.json as you need them, lazy-load the route, and skip the unmaintained editor packages. To check your component's output against a standard renderer, paste the same Markdown into the editor or run it through the Markdown to HTML tool and diff the markup.