MarkdownService
The root-provided service behind the component and the pipe — use it directly for headless rendering.
MarkdownService is @Injectable({ providedIn: 'root' }). It owns the markdown-it instance, the
Shiki highlighter and the block cache — which is why the cache is shared by every
MarkdownComponent in the application.
Inject it when you need HTML outside a template: pre-rendering, a custom pipe, a report generator, an export to email.
import { MarkdownService, type RenderedBlock } from 'shikidown';
@Injectable({ providedIn: 'root' })
export class ContentService {
private readonly md = inject(MarkdownService);
async toHtml(markdown: string): Promise<string> {
return this.md.parseAsync(markdown);
}
async toBlocks(markdown: string): Promise<RenderedBlock[]> {
return this.md.parseBlocksAsync(markdown);
}
}Methods
| Method | Signature | Description |
|---|---|---|
initialize | () => Promise<void> | Loads Shiki and builds the markdown-it instance. |
parseAsync | (content: string) => Promise<string> | Renders the whole document, returns raw HTML. |
parse | (content: string) => string | Synchronous render. Throws before initialisation. |
parseBlocksAsync | (content: string) => Promise<RenderedBlock[]> | Renders as independent blocks, with LRU caching. |
clearCache | () => void | Empties the block cache. |
initialize
initialize(): Promise<void>Dynamically imports Shiki, creates the highlighter with the configured themes and languages, builds
the markdown-it instance, resolves and applies your plugins, and installs the mermaid fence rule.
Plugins declared as { load: () => import('…') } are fetched here, in parallel, before any of them
is applied.
Idempotent, and safe to call concurrently — the promise is memoised, so parallel callers share one
initialisation. Every async method calls it for you; you only need it explicitly before
parse().
In a server environment no highlighter is created. The document still renders, with code
fences emitted as plain <pre class="shiki"><code> blocks.
parseAsync
parseAsync(content: string): Promise<string>Renders the whole document and returns raw HTML — a string, not SafeHtml. Binding it to
[innerHTML] yourself means going through DomSanitizer; sanitising will strip embedded Custom
Elements and Shiki's inline styles, so use bypassSecurityTrustHtml and treat the source as
trusted.
parse
parse(content: string): stringSynchronous render. Throws if the service has not finished initialising:
[shikidown] MarkdownService not initialized. Call parseAsync() first.Use it in code that cannot be asynchronous, after awaiting initialize() once:
await this.md.initialize();
const html = this.md.parse('# Now this is safe');parseBlocksAsync
parseBlocksAsync(content: string): Promise<RenderedBlock[]>Splits the document into independent root blocks and renders each one, serving unchanged blocks
from the LRU cache. This is what MarkdownComponent calls in incremental mode.
Each RenderedBlock carries the FNV-1a hash of its source text and
its raw HTML. The hash is a stable identity: use it as the tracking key if you drive your own
rendering loop.
const blocks = await this.md.parseBlocksAsync(markdown);
// [{ hash: '1f4a3b2c', html: '<h1>Title</h1>\n' }, …]See Incremental rendering for what the pipeline buys you and how to keep the DOM stable across renders.
clearCache
clearCache(): voidEmpties the block cache. Needed after a runtime theme change, since blocks are keyed on source text alone and would otherwise keep their old colours.
this.md.clearCache();A dark/light toggle does not require this: with a { dark, light } theme pair both colour sets are
already present in the rendered HTML.
Defaults
Values used when provideMarkdown() is absent, or when an option is not set:
| Default | |
|---|---|
| Theme | { dark: 'github-dark', light: 'poimandres' } |
| Languages | 16 common languages — see Configuration |
| markdown-it options | { html: true, linkify: true, typographer: true } |
| Block cache capacity | 256 |