Render Angular Components in Markdown

Dharmen Shah - Jul 5 - - Dev Community

This example demonstrates renderring of markdown in angular and also how to render angular components in markdown.

First, we will setup <markdown-render> component to render .md files. And then we will look how to render angular components.

Markdown Renderer

Install needed dependencies:

npm i highlight.js marked marked-highlight
Enter fullscreen mode Exit fullscreen mode

Step 1: Create markdown-renderer/highlight-code-block.ts

This function will be used to highlight code in our markdown file

import highlightJs from 'highlight.js';

export function highlightCodeBlock(code: string, language: string | undefined) {
  if (language) {
    return highlightJs.highlight(code, {
      language,
    }).value;
  }

  return code;
}

Enter fullscreen mode Exit fullscreen mode

Step 2: Create markdown-renderer/transform-markdown.ts

This function will be used to convert markdown to html.

import { marked } from 'marked';
import { markedHighlight } from 'marked-highlight';
import { highlightCodeBlock } from './highlight-code-block';

marked.use(markedHighlight({ highlight: highlightCodeBlock }));

export const markdownToHtml = (content: string) => {
  return marked(content);
};

Enter fullscreen mode Exit fullscreen mode

Step 3: Create markdown-renderer/markdown.service.ts

This service will be used in the component to read .md file from local or external location and then convert it to html.

import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { map } from 'rxjs';
import { markdownToHtml } from './transform-markdown';

@Injectable({
  providedIn: 'root',
})
export class MarkdownService {
  private httpClient = inject(HttpClient);

  htmlContent(src: string) {
    return this.httpClient.get(src, { responseType: 'text' }).pipe(
      map((markdownContent) => {
        return markdownToHtml(markdownContent);
      })
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

Step 4: Create markdown-renderer/markdown-renderer.ts

Finally, this will be out component which we can use to render markdown files.

import { Component, ElementRef, effect, inject, input } from '@angular/core';
import { MarkdownService } from './markdown.service';
import { take } from 'rxjs';
import highlightJs from 'highlight.js';

@Component({
  selector: 'markdown-renderer',
  template: 'Loading document...',
  standalone: true,
})
export class MarkdownRendererComponent {
  src = input.required<string>();
  textContent = '';

  private _elementRef = inject<ElementRef>(ElementRef);

  private markdownService = inject(MarkdownService);

  constructor() {
    effect(() => {
      const src = this.src();
      this.setDataFromSrc(src);
    });
  }

  setDataFromSrc(src: string) {
    this.markdownService
      .htmlContent(src)
      .pipe(take(1))
      .subscribe((htmlContent) => {
        this.updateDocument(htmlContent as string);
      });
  }

  updateDocument(rawHTML: string) {
    this._elementRef.nativeElement.innerHTML = rawHTML;
    this.textContent = this._elementRef.nativeElement.textContent;
    highlightJs.highlightAll();
  }
}

Enter fullscreen mode Exit fullscreen mode

Step 5: Provide HTTP

bootstrapApplication(App, {
  providers: [
    provideHttpClient(withFetch())
  ],
});
Enter fullscreen mode Exit fullscreen mode

Step 6: Usage

Now, wherever we want to render markdown, we will simply use <markdown-renderer>:

import { Component } from '@angular/core';
import { MarkdownRendererComponent } from './markdown-renderer/markdown-renderer';

@Component({
  selector: 'article',
  standalone: true,
  template: `<markdown-renderer src="/assets/article.md"></markdown-renderer>`,
  imports: [MarkdownRendererComponent],
})
export class ArticleComponent {}

Enter fullscreen mode Exit fullscreen mode

Angular Components in Markdown

Install needed dependencies:

npm i @angular/elements
Enter fullscreen mode Exit fullscreen mode

Step 1: Create custom-elements.service.ts

This service will used to convert angular components to custom elements, so that we can easily use angular components in in .md file.

import { inject, Injectable, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { SubscribeComponent } from './components/subscribe';
import { CounterComponent } from './components/counter';

@Injectable({ providedIn: 'root' })
export class CustomElementsService {
  private _injector = inject(Injector);

  setupCustomElements() {
    const subscribeElement = createCustomElement(SubscribeComponent, {
      injector: this._injector,
    });
    customElements.define('subscribe-component', subscribeElement);

    const counterElement = createCustomElement(CounterComponent, {
      injector: this._injector,
    });
    customElements.define('counter-component', counterElement);
  }
}

Enter fullscreen mode Exit fullscreen mode

Step 2: Call setupCustomElements through APP_INITIALIZER

As we want custom elements present from the initialization, we will use APP_INITIALIZER.

bootstrapApplication(App, {
  providers: [
    provideHttpClient(withFetch()),
    {
      provide: APP_INITIALIZER,
      useFactory: initializeCustomElements,
      multi: true,
      deps: [CustomElementsService],
    },
  ],
});
Enter fullscreen mode Exit fullscreen mode

Step 3: Usage

Finally, you can simply use your custom element in .md file it will render the angular component, like below:

<subscribe-component></subscribe-component>
<counter-component></counter-component>
Enter fullscreen mode Exit fullscreen mode

Code

Support free content creation

Even though the courses and articles are available at no cost, your support in my endeavor to deliver top-notch educational content would be highly valued. Your decision to contribute aids me in persistently improving the course, creating additional resources, and maintaining the accessibility of these materials for all. I'm grateful for your consideration to contribute and make a meaningful difference!

🙏 Support free content creation

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .