Angular port of streamdown - streaming markdown renderer for AI-powered applications
npm install ngx-streamdownAngular port of streamdown - A streaming markdown renderer optimized for AI-powered applications.

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.
- 🚀 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
``bash`
npm install ngx-streamdown ngx-markdown
- @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)
For standalone components (Angular 14+):
`typescript
// 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:
`typescript
// 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 {}
`
Add to your angular.json or import in your global styles:
`css`
/ styles.css /
@import 'katex/dist/katex.css'; / For math support /
`typescript
import { Component } from '@angular/core';
import { StreamingMarkdownComponent } from 'ngx-streamdown';
@Component({
selector: 'app-chat',
standalone: true,
imports: [StreamingMarkdownComponent],
template:
[mode]="'streaming'"
[isAnimating]="isStreaming">
`
})
export class ChatComponent {
markdown = '# Hello World!';
isStreaming = false;
}
`typescript
import { Component } from '@angular/core';
import { StreamingMarkdownComponent } from 'ngx-streamdown';
@Component({
selector: 'app-ai-chat',
standalone: true,
imports: [StreamingMarkdownComponent],
template:
})
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;
}
}
`$3
`typescript
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:
})
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
$3
| 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
$3
Injectable service for processing markdown:
`typescript
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 remend
- parseIntoBlocks(markdown: string): string[] - Parse markdown into renderable blocks
- hasIncompleteSyntax(markdown: string): boolean - Check if markdown has incomplete syntax
- updateMarkdown(markdown: string, config?: StreamdownConfig): void - Update the markdown observable
- reset(): void - Reset the markdown contentStyling
The component includes default styles, but you can customize them using CSS variables:
`css
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:
`typescript
[content]="markdown"
className="my-custom-markdown">
``css
.my-custom-markdown {
font-family: 'Georgia', serif;
font-size: 1.125rem;
}.my-custom-markdown h1 {
color: #2563eb;
}
`Advanced Usage
$3
`typescript
[content]="markdown"
[remendOptions]="{
bold: true,
italic: true,
inlineCode: true,
links: false,
images: false
}">
`$3
For small content or when you want single-block rendering:
`typescript
[content]="markdown"
[enableBlockParsing]="false">
`$3
Adjust rendering frequency:
`typescript
[content]="markdown"
[debounceTime]="50">
`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
1. Use OnPush strategy - The component already uses OnPush change detection
2. Adjust debounceTime - Lower values = smoother but more CPU intensive
3. Disable block parsing for short content
4. Use trackBy - Built-in for efficient ngFor rendering
5. 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 demonstrationDemo App
A full Angular demo app lives in demo-app. It showcases streaming controls, presets, and theming.
`bash
Build the library first
npm install
npm run buildRun the demo
cd demo-app
npm install
npm start
`Development
`bash
Install dependencies
npm installBuild the library
npm run buildRun tests
npm testLint
npm run lint
``- streamdown - Original React version
- remend - Self-healing markdown parser
- ngx-markdown - Angular markdown component
MIT
Contributions are welcome! Please feel free to submit a Pull Request.