Skip to content

Pages

A `.md` file as an Angular component template, with `templateMD`.

A page is an ordinary Angular component whose template is the Markdown file beside it — declared the way templateUrl declares an .html file.

text
button.page.ts     the component: its fields are what the Markdown binds against
button.page.md     the template
button.page.css    its stylesheet, which reaches the rendered Markdown

The shape

ts
import { Component } from '@angular/core';
import templateMD from './button.page.md';
 
@Component({
  selector: 'app-button-page',
  templateMD,
  styleUrl: './button.page.css',
  components: [AppButton],
})
export class ButtonPage {
  count = 0;
  bump(): void { this.count++; }
}

Three things follow from it being an ordinary component:

  1. The class is the binding context. {{ count }} and bump() are those very members — there is no context object to pass or keep in sync.

  2. styleUrl reaches the Markdown. See Page styles.

  3. It routes, injects and tests like a component, because it is one.

How templateMD works

Angular's compiler accepts only what it knows: a @Component argument must be a literal object, its values must be statically evaluable, and an unknown key is dropped before anything reaches the runtime. A Markdown string imported at build time is none of those.

So templateMD is a build-time transform, not metadata Angular ever sees. Before the compiler reads the file, Blasdoc rewrites it into the page you would otherwise write by hand:

ts
@Component({ selector: 'app-button-page', template: '', styleUrl: './button.page.css' })
export class ButtonPage {
  #blasdocPage = blasdocPageInit(this, { templateMD, components: [AppButton] });
  count = 0;
}

Every edit keeps its line, so a stack trace still lands where you wrote the code. Runtime-first is untouched: templateMD is still the Markdown string, parsed when the page renders. Installing it is one line — see Build integration.

Without a build step

templateMD needs the transform. Where that is not welcome, blasdocPage() gives the same page as a base class — plain TypeScript, stock ng build, no editor caveat:

ts
import { blasdocPage } from '@blasdoc/angular';
 
@Component({
  selector: 'app-button-page',
  template: '',
  styleUrl: './button.page.css',
})
export class ButtonPage extends blasdocPage({
  templateMD,
  components: [AppButton],
}) {
  count = 0;
  bump(): void { this.count++; }
}

From injected services

Extend BlasdocPage directly when the configuration cannot be a constant:

ts
import { BlasdocPage } from '@blasdoc/angular';
 
@Component({ selector: 'app-remote-page', template: '' })
export class RemotePage extends BlasdocPage {
  private readonly content = inject(ContentService);
  readonly templateMD = this.content.markdown();
  readonly components = [AppButton];
}

Reading a page back

Two functions work on a page written any of the three ways:

ts
import { blasdocPageDiagnostics, blasdocPageHeadings } from '@blasdoc/angular';
 
blasdocPageDiagnostics(page); // every problem, parse and runtime
blasdocPageHeadings(page);    // the outline, from the IR the page parsed

The table of contents on the right of this page is blasdocPageHeadings — read from the same IR the renderer used, so it cannot describe a document that is not there.