npm.io
0.4.1 • Published 3d ago

streamdown-angular

Licence
Apache-2.0
Version
0.4.1
Deps
10
Size
275 kB
Vulns
0
Weekly
0

streamdown-angular

Stream-safe Markdown rendering for Angular — built for AI chat UIs

Angular port of Vercel Streamdown. Drop‑in <ngx-streamdown> component powered by signals, standalone components and OnPush.

npm license Angular types


Render Markdown that arrives one token at a time. streamdown-angular parses Markdown into a sanitized HAST tree and renders it as real Angular DOM (never innerHTML of untrusted content). As text streams in, incomplete Markdown — **bold, [half link, unterminated code fences — is auto‑completed, and a diffing renderer updates only what changed (no flicker, scroll & selection preserved).

<ngx-streamdown [content]="response()" [caret]="true" />

Contents

Why streamdown-angular

Feature
Streaming‑first — Markdown is split into memoized blocks; only the changed block re‑renders. Incomplete Markdown is fixed via remend.
Incremental rendering — HAST diffing keeps unchanged DOM in place; no flicker, scroll/selection/focus preserved, mapped components keep their instance.
Safe by defaultrehype-sanitize in the pipeline; text via Renderer2.createText, never raw innerHTML.
GFM — tables, strikethrough, task lists, autolinks.
Code blocksShiki highlighting (light/dark) + copy & download buttons. Default on.
Tables — copy as Markdown/CSV, download CSV. Default on.
Math — KaTeX $inline$ and $block$. Opt‑in.
Diagrams — Mermaid. Opt‑in.
Streaming caret + optional fade‑in for new tokens.
Link safety — external links open via a confirmation modal; always rel="noopener noreferrer".
Images — loading skeleton + error fallback.
i18n / icons / prefix — fully overridable via providers.
RTL/LTR auto‑detection.

Install

npm i streamdown-angular

Heavy features are optional peer dependencies — install only what you use:

npm i shiki                            # code highlighting (recommended)
npm i mermaid                          # diagrams
npm i katex remark-math rehype-katex   # math

Requires Angular 21+.

Quick start

import { Component, signal } from '@angular/core';
import { StreamdownComponent } from 'streamdown-angular';

@Component({
  selector: 'app-chat',
  standalone: true,
  imports: [StreamdownComponent],
  template: `<ngx-streamdown [content]="markdown()" />`,
})
export class ChatComponent {
  markdown = signal('# Hello 👋\n\nStreaming **markdown** with `code`, tables and math.');
}

That's it — no module imports, no NgModule. Code blocks and tables work out of the box.

Streaming from an API

Just keep growing the content signal as chunks arrive. Turn on caret while the response is in flight:

@Component({
  selector: 'app-chat',
  standalone: true,
  imports: [StreamdownComponent],
  template: `<ngx-streamdown [content]="answer()" [caret]="streaming()" />`,
})
export class ChatComponent {
  readonly answer = signal('');
  readonly streaming = signal(false);

  async ask(prompt: string): Promise<void> {
    this.answer.set('');
    this.streaming.set(true);
    for await (const chunk of streamFromApi(prompt)) {
      this.answer.update((text) => text + chunk);   // diffing renders only the delta
    }
    this.streaming.set(false);
  }
}

Inputs

Input Type Default Description
content string '' Markdown source — grow it as tokens stream in.
parseIncompleteMarkdown boolean true Auto‑complete unterminated Markdown while streaming.
caret boolean false Blinking "typing" caret after the last block.

Optional plugins

Register once in your app providers:

import { ApplicationConfig } from '@angular/core';
import {
  provideStreamdownMath,
  provideStreamdownMermaid,
  provideStreamdownAnimation,
} from 'streamdown-angular';

export const appConfig: ApplicationConfig = {
  providers: [
    provideStreamdownMath(),       // KaTeX:  $E=mc^2$  and  $\int_0^\infty …$
    provideStreamdownMermaid(),    // ```mermaid fenced diagrams
    provideStreamdownAnimation(),  // fade-in newly streamed elements
  ],
};

Math needs KaTeX CSS. Add it to angular.jsonstyles:

"node_modules/katex/dist/katex.min.css"
Provider Adds Extra dependency
provideStreamdownMath() KaTeX math katex, remark-math, rehype-katex
provideStreamdownMermaid() Mermaid diagrams mermaid
provideStreamdownAnimation() Fade‑in for new DOM (respects prefers-reduced-motion)
provideStreamdownLinkSafety() Confirmation modal for external links

Styling

Zero‑config — no Tailwind, no CSS import needed. All styles are plain CSS, scoped under .ngx-streamdown, and ship inside the component (ViewEncapsulation.None). They apply automatically when the component is used and never leak into the rest of your app.

Customize by overriding the scoped CSS in your own stylesheet:

.ngx-streamdown h1 { font-size: 2.25rem; color: #111; }
.ngx-streamdown a  { color: rebeccapurple; }

Or add classes per tag / swap whole components from TypeScript:

import { ELEMENT_CLASSES, COMPONENT_MAP } from 'streamdown-angular';

ELEMENT_CLASSES['h1'] = 'my-heading';            // extra class on every <h1>
COMPONENT_MAP.set('pre', MyCodeBlockComponent);  // replace the code-block renderer

Internationalization & customization

import {
  provideStreamdownTranslations,
  provideStreamdownIcons,
  provideStreamdownPrefix,
} from 'streamdown-angular';

providers: [
  // i18n — every UI string (e.g. Uzbek)
  provideStreamdownTranslations({
    copy: 'Nusxa', copied: 'Nusxalandi', download: 'Yuklab olish',
    linkWarningTitle: 'Tashqi havolani ochasizmi?', open: 'Ochish', cancel: 'Bekor qilish',
  }),
  // swap the copy / download / … SVG icons
  provideStreamdownIcons({ copy: '<svg>…</svg>' }),
  // namespace CSS hooks
  provideStreamdownPrefix('myapp'),
]

Low‑level API

Render Markdown or HAST yourself
Export Purpose
MarkdownService.toHast(md, { remend? }) Markdown → sanitized HAST Root
HastRendererComponent Renders a HAST Root to Angular DOM (with diffing)
HastRendererService The reconciling HAST → DOM engine (reconcile, renderNodes, clear)
parseMarkdownIntoBlocks(md) Split Markdown into top‑level blocks
detectDirection(text) 'rtl' / 'ltr'
extractTableData · toCsv · toTsv · toMarkdown Table export helpers
STREAMDOWN_PIPELINE DI token to add custom remark/rehype plugins to the pipeline
const md = inject(MarkdownService);
const hast = md.toHast('# Hi');          // → HAST Root

How it works

Data flow (React → Angular mapping)
content ──▶ parseMarkdownIntoBlocks ──▶ @for(block) [track $index, OnPush]
        └─▶ MarkdownService.toHast
              remark-parse → gfm → [plugins] → remark-rehype
                            → rehype-raw → [plugins] → rehype-sanitize
        └─▶ HastRendererComponent ──▶ HastRendererService.reconcile
              ├─ text node    → Renderer2.createText        (patched in place)
              ├─ generic tag  → Renderer2.createElement     (attrs diffed)
              └─ pre/table/img→ Angular component           (node input updated)

It mirrors Streamdown's React data flow — useMemo chain → computed, memo()OnPush + @for track — and replaces React's hast-util-to-jsx-runtime with a native, diffing Angular DOM renderer.

License

Apache‑2.0 — Angular port of Streamdown. Original work 2023 Vercel, Inc. (see NOTICE).

Built with for the Angular community

Keywords