Components
Angular components inside Markdown: registration, children, and how a tag is told apart from HTML.
A tag in your Markdown can become a real Angular component. This is the centre of Blasdoc, so it is worth being precise about how a tag is recognised, how it is resolved, and what happens when it is not.
Using one
<app-button variant="primary">Comprar</app-button>Import it, then use it
A page is an ordinary Angular component, so the components its Markdown may use
are the ones in its imports — exactly as they would be for an HTML template.
Nothing is registered, and no name is written twice.
@Component({
selector: 'app-guide-page',
templateMD,
imports: [AppButton, AppCallout],
})
export class GuidePage {}<app-button variant="primary">Comprar</app-button>The tag comes from each component's own selector, so it cannot drift from the
class. Directives and pipes may sit in imports too and are simply left out:
Blasdoc resolves a Markdown tag against a plain element selector.
When to write components instead
imports covers the ordinary case. The explicit list is there for the two
things a selector cannot express, and it wins over imports:
A lazy loader, which arrives only when a document uses it:
components: [
{ selector: 'app-chart', load: () => import('./chart').then((m) => m.Chart) },
]Or a tag that differs from the component's own selector:
components: { 'app-cta': AppButton }Application-wide, and overriding Blasdoc's own
provideBlasdoc() registers for every document at once. That is what it is for
now: the components a whole site shares, and — more usefully — replacing the
structural components Blasdoc itself renders, such as the code block and its
tabs.
provideBlasdoc({
components: {
'blasdoc-callout': MyCallout,
'app-button': AppButton,
},
});A page's own imports win over anything registered this way, so one page can
differ from the rest without touching the application.
Children
Children are projected through <ng-content />, and they are Markdown:
<app-callout type="warning">
This is **Markdown** inside a component, with a [link](/docs).
</app-callout>Nesting works to any depth, and a component may span several Markdown blocks.
Self-closing
<app-spacer />HTML is not a component
<div> stays a div. The rule is the tag name: a name containing a dash is a
component, everything else is HTML.
<div>A plain div, rendered as HTML.</div>You can change the rule:
provideBlasdoc({
parseOptions: {
isComponentName: (name) => name.startsWith('app-'),
},
});Unknown components
The parser does not know your registry — it records a component node either way. The runtime is what reports the problem:
BLASDOC_UNKNOWN_COMPONENT Unknown component "app-missing".Nothing is instantiated on a guess. See Diagnostics.
The Markdown boundary
CommonMark only recognises HTML-shaped tags, so <app-callout type="warning">
arrives as a raw HTML block while <app-button [disabled]="x"> arrives as plain
text — [disabled] is not a valid HTML attribute name. Blasdoc re-reads both
through its own tokenizer, so the two spellings produce the same IR.
Two consequences worth knowing:
A paragraph is transparent to tags: it is only materialised once no tag
crossed its boundary, so a component can span several blocks, and a paragraph
holding nothing but tags is dropped rather than wrapping your component in a
stray <p>.