ngx-streamdown
January 19, 2026 ยท View on GitHub
Angular port of streamdown - A streaming markdown renderer optimized for AI-powered applications.
Overview
ngx-streamdown brings the power of streaming markdown rendering to Angular applications. Built on top of ngx-markdown, it handles incomplete Markdown syntax gracefully during real-time streaming from AI models, providing seamless formatting even with partial or unterminated Markdown blocks.
Features
- ๐ Angular-native - Built with Angular signals and standalone components
- ๐ Streaming-optimized - Handles incomplete Markdown gracefully using remend
- ๐จ Progressive rendering - Parses markdown into blocks for better performance
- ๐ GitHub Flavored Markdown - Full GFM support via ngx-markdown
- ๐ข Math rendering - LaTeX equations via KaTeX
- ๐ฏ TypeScript - Full type safety with TypeScript
- โก Performance optimized - Debounced rendering and change detection
- ๐ก๏ธ OnPush strategy - Optimized change detection for better performance
Installation
npm install ngx-streamdown ngx-markdown
Peer Dependencies
@angular/common^17.0.0 || ^18.0.0@angular/core^17.0.0 || ^18.0.0ngx-markdown^17.0.0 || ^18.0.0rxjs^7.8.0remend(automatically installed)
Setup
1. Import Required Modules
For standalone components (Angular 14+):
// app.config.ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { provideMarkdown } from 'ngx-markdown';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideHttpClient(),
provideMarkdown(),
],
};
For NgModule-based apps:
// app.module.ts
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { MarkdownModule } from 'ngx-markdown';
@NgModule({
imports: [
HttpClientModule,
MarkdownModule.forRoot(),
],
})
export class AppModule {}
2. Add Styles (Optional)
Add to your angular.json or import in your global styles:
/* styles.css */
@import 'katex/dist/katex.css'; /* For math support */
Usage
Basic Example
import { Component } from '@angular/core';
import { StreamingMarkdownComponent } from 'ngx-streamdown';
@Component({
selector: 'app-chat',
standalone: true,
imports: [StreamingMarkdownComponent],
template: `
<ngx-streamdown
[content]="markdown"
[mode]="'streaming'"
[isAnimating]="isStreaming">
</ngx-streamdown>
`
})
export class ChatComponent {
markdown = '# Hello **World**!';
isStreaming = false;
}
Streaming Example
import { Component } from '@angular/core';
import { StreamingMarkdownComponent } from 'ngx-streamdown';
@Component({
selector: 'app-ai-chat',
standalone: true,
imports: [StreamingMarkdownComponent],
template: `
<div class="chat-container">
<ngx-streamdown
[content]="streamingContent"
[mode]="'streaming'"
[parseIncompleteMarkdown]="true"
[isAnimating]="isStreaming"
[showCaret]="true"
[caret]="'block'"
[enableKatex]="true">
</ngx-streamdown>
</div>
`
})
export class AIChatComponent {
streamingContent = '';
isStreaming = false;
async streamFromAI() {
this.isStreaming = true;
// Simulate streaming from an AI API
const fullResponse = "# AI Response\n\nThis is **streaming** from an AI!";
for (let i = 0; i < fullResponse.length; i++) {
this.streamingContent = fullResponse.substring(0, i + 1);
await new Promise(resolve => setTimeout(resolve, 50));
}
this.isStreaming = false;
}
}
With Real AI Streaming
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { StreamingMarkdownComponent } from 'ngx-streamdown';
@Component({
selector: 'app-ai-chat',
standalone: true,
imports: [StreamingMarkdownComponent],
template: `
<ngx-streamdown
[content]="response"
[mode]="'streaming'"
[isAnimating]="isStreaming">
</ngx-streamdown>
`
})
export class AIChatComponent {
response = '';
isStreaming = false;
constructor(private http: HttpClient) {}
async streamResponse(prompt: string) {
this.isStreaming = true;
this.response = '';
const response = await fetch('/api/ai/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt }),
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
if (!reader) return;
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
this.response += chunk;
}
this.isStreaming = false;
}
}
Component API
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
content | string | '' | The markdown content to render |
mode | 'static' | 'streaming' | 'streaming' | Rendering mode |
parseIncompleteMarkdown | boolean | true | Apply remend to handle incomplete syntax |
className | string | '' | Additional CSS classes |
enableKatex | boolean | true | Enable KaTeX math rendering |
enableMermaid | boolean | false | Enable Mermaid diagrams |
isAnimating | boolean | false | Whether content is currently streaming |
showCaret | boolean | true | Show cursor caret when streaming |
caret | 'block' | 'bar' | 'underscore' | 'block' | Caret style |
debounceTime | number | 16 | Debounce time in ms (~60fps) |
remendOptions | RemendOptions | undefined | Options for remend parser |
enableBlockParsing | boolean | true | Parse into blocks for progressive rendering |
Service API
StreamingMarkdownService
Injectable service for processing markdown:
import { StreamingMarkdownService } from 'ngx-streamdown';
@Component({
// ...
})
export class MyComponent {
constructor(private streamingService: StreamingMarkdownService) {}
processMarkdown(text: string) {
// Process with remend
const processed = this.streamingService.processMarkdown(text, {
mode: 'streaming',
parseIncompleteMarkdown: true,
});
// Parse into blocks
const blocks = this.streamingService.parseIntoBlocks(processed);
// Check for incomplete syntax
const hasIncomplete = this.streamingService.hasIncompleteSyntax(text);
}
}
Methods
processMarkdown(markdown: string, config?: StreamdownConfig): string- Process markdown with optional remendparseIntoBlocks(markdown: string): string[]- Parse markdown into renderable blockshasIncompleteSyntax(markdown: string): boolean- Check if markdown has incomplete syntaxupdateMarkdown(markdown: string, config?: StreamdownConfig): void- Update the markdown observablereset(): void- Reset the markdown content
Styling
The component includes default styles, but you can customize them using CSS variables:
ngx-streamdown {
--font-family: 'Inter', system-ui, sans-serif;
--font-size: 1rem;
--line-height: 1.6;
--text-color: #1f2937;
--link-color: #2563eb;
--code-bg: rgba(175, 184, 193, 0.2);
--border-color: #e5e7eb;
--table-header-bg: #f9fafb;
}
Or use custom classes:
<ngx-streamdown
[content]="markdown"
className="my-custom-markdown">
</ngx-streamdown>
.my-custom-markdown {
font-family: 'Georgia', serif;
font-size: 1.125rem;
}
.my-custom-markdown h1 {
color: #2563eb;
}
Advanced Usage
Custom Remend Options
<ngx-streamdown
[content]="markdown"
[remendOptions]="{
bold: true,
italic: true,
inlineCode: true,
links: false,
images: false
}">
</ngx-streamdown>
Disable Block Parsing
For small content or when you want single-block rendering:
<ngx-streamdown
[content]="markdown"
[enableBlockParsing]="false">
</ngx-streamdown>
Custom Debounce Time
Adjust rendering frequency:
<ngx-streamdown
[content]="markdown"
[debounceTime]="50"> <!-- ~20fps -->
</ngx-streamdown>
Comparison with React Version
| Feature | React (streamdown) | Angular (ngx-streamdown) |
|---|---|---|
| Streaming Support | โ | โ |
| Remend Integration | โ | โ |
| Block Parsing | โ | โ |
| KaTeX Support | โ | โ (via ngx-markdown) |
| Mermaid Support | โ | โ (via ngx-markdown) |
| Code Highlighting | Shiki | Prism (ngx-markdown) |
| Framework | React | Angular 17+ |
Performance Tips
- Use OnPush strategy - The component already uses OnPush change detection
- Adjust debounceTime - Lower values = smoother but more CPU intensive
- Disable block parsing for short content
- Use trackBy - Built-in for efficient ngFor rendering
- Virtual scrolling - For very long conversations, wrap in a virtual scroll container
Examples
See the examples directory for complete working examples:
streaming-example.component.ts- Basic streaming demonstration
Demo App
A full Angular demo app lives in demo-app. It showcases streaming controls, presets, and theming.
# Build the library first
npm install
npm run build
# Run the demo
cd demo-app
npm install
npm start
Development
# Install dependencies
npm install
# Build the library
npm run build
# Run tests
npm test
# Lint
npm run lint
Related Projects
- streamdown - Original React version
- remend - Self-healing markdown parser
- ngx-markdown - Angular markdown component
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.