본문으로 건너뛰기

Content Routes

Analog also supports using markdown content as routes, and rendering markdown content in components.

Setup

In the src/app/app.config.ts, add the provideContent() function, along with the withMarkdownRenderer() feature to the providers array when bootstrapping the application.

import { ApplicationConfig } from '@angular/core';
import { provideContent, withMarkdownRenderer } from '@analogjs/content';

export const appConfig: ApplicationConfig = {
providers: [
// ... other providers
provideContent(withMarkdownRenderer()),
],
};

Defining Content Routes

Content routes include support for frontmatter, metatags, and syntax highlighting with PrismJS.

The example route below in src/app/pages/about.md defines an /about route.

---
title: About
meta:
- name: description
content: About Page Description
- property: og:title
content: About
---

## About Analog

Analog is a meta-framework for Angular.

[Back Home](./)

PrismJS Syntax Highlighting

Analog supports syntax highlighting with PrismJS. To enable syntax highlighting with PrismJS, add withPrismHighlighter() to the provideContent() function in app.config.ts.

import { ApplicationConfig } from '@angular/core';
import { provideContent, withMarkdownRenderer } from '@analogjs/content';
+ import { withPrismHighlighter } from '@analogjs/content/prism-highlighter';

export const appConfig: ApplicationConfig = {
providers: [
// ... other providers
- provideContent(withMarkdownRenderer()),
+ provideContent(withMarkdownRenderer(), withPrismHighlighter()),
],
};

Import a Prism theme into to your global stylesheet:

@import 'prismjs/themes/prism.css';

Using the diff Highlight Plugin

Analog supports highlighting diff changes with PrismJS.

Add the prism-diff language to the additionalLangs in the analog plugin:

import { defineConfig } from 'vite';
import analog from '@analogjs/platform';

export default defineConfig({
// ...
plugins: [
analog({
content: {
prismOptions: {
additionalLangs: ['prism-diff'],
},
},
}),
],
});

Add the diff-highlight plugin import to the app.config.ts:

import 'prismjs/plugins/diff-highlight/prism-diff-highlight';

Use the diff language tag to highlight them or diff-<language> to highlight the diff changes in a specific language.

```diff
- This is a sentence.
+ This is a longer sentence.
```

```diff-typescript
- const foo = 'bar';
+ const foo = 'baz';
```

To highlight changed line backgrounds instead of just the text, add this import to your global stylesheet:

@import 'prismjs/plugins/diff-highlight/prism-diff-highlight.css';

Shiki Syntax Highlighting

Analog also supports syntax highlighting with Shiki. To enable syntax highlighting with Shiki, add withShikiHighlighter() to the provideContent() function in app.config.ts.

import { ApplicationConfig } from '@angular/core';
import { provideContent, withMarkdownRenderer } from '@analogjs/content';
+ import { withShikiHighlighter } from '@analogjs/content/shiki-highlighter';

export const appConfig: ApplicationConfig = {
providers: [
// ... other providers
- provideContent(withMarkdownRenderer()),
+ provideContent(withMarkdownRenderer(), withShikiHighlighter()),
],
};

To enable build-time syntax highlighting with shiki, configure the analog plugin in the vite.config.ts.

import { defineConfig } from 'vite';
import analog from '@analogjs/platform';

export default defineConfig({
// ...
plugins: [
analog({
content: {
highlighter: 'shiki',
},
}),
],
});

Configure Shiki Highlighter

Please check out Shiki Documentation for more information on configuring Shiki.

To configure Shiki, you can pass options to the shikiOptions object.

import { defineConfig } from 'vite';
import analog from '@analogjs/platform';

export default defineConfig({
// ...
plugins: [
analog({
content: {
highlighter: 'shiki',
shikiOptions: {
highlight: {
// alternate theme
theme: 'ayu-dark'
}
highlighter: {
// add more languages
additionalLangs: ['mermaid'],
},
},
},
}),
],
});

By default, shikiOptions has the following options.

{
"container": "%s",
"highlight": {
"theme": "github-dark"
}
"highlighter": {
"langs": [
"json",
"ts",
"tsx",
"js",
"jsx",
"html",
"css",
"angular-html",
"angular-ts",
],
"themes": ["github-dark", "github-light"]
}
}

Defining Content Files

For more flexibility, markdown content files can be provided in the src/content folder. Here you can list markdown files such as blog posts.

---
title: My First Post
slug: 2022-12-27-my-first-post
description: My First Post Description
coverImage: https://images.unsplash.com/photo-1493612276216-ee3925520721?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=464&q=80
---

Hello World

Using the Content Files List

To get a list using the list of content files in the src/content folder, use the injectContentFiles<Attributes>(filterFn?: InjectContentFilesFilterFunction<Attributes>) function from the @analogjs/content package in your component. To narrow the files, you can use the filterFn predicate function as an argument. You can use the InjectContentFilesFilterFunction<T> type to set up your predicate.

import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
import { injectContentFiles } from '@analogjs/content';
import { NgFor } from '@angular/common';

export interface PostAttributes {
title: string;
slug: string;
description: string;
coverImage: string;
}

@Component({
standalone: true,
imports: [RouterOutlet, RouterLink, NgFor],
template: `
<ul>
<li *ngFor="let post of posts">
<a [routerLink]="['/blog', 'posts', post.slug]">{{
post.attributes.title
}}</a>
</li>
</ul>
`,
})
export default class BlogComponent {
readonly posts = injectContentFiles<PostAttributes>((contentFile) =>
contentFile.filename.includes('/src/content/blog/')
);
}

Using the Analog Markdown Component

Analog provides a MarkdownComponent and injectContent() function for rendering markdown content with frontmatter.

The injectContent() function uses the slug route parameter by default to get the content file from the src/content folder.

// /src/app/pages/blog/posts.[slug].page.ts
import { injectContent, MarkdownComponent } from '@analogjs/content';
import { AsyncPipe, NgIf } from '@angular/common';
import { Component } from '@angular/core';

export interface PostAttributes {
title: string;
slug: string;
description: string;
coverImage: string;
}

@Component({
standalone: true,
imports: [MarkdownComponent, AsyncPipe, NgIf],
template: `
<ng-container *ngIf="post$ | async as post">
<h1>{{ post.attributes.title }}</h1>
<analog-markdown [content]="post.content"></analog-markdown>
</ng-container>
`,
})
export default class BlogPostComponent {
readonly post$ = injectContent<PostAttributes>();
}

Using A Resolver For Metatags

In your route configuration, you can use the RouteMeta object to resolve meta tags for a route. This is done by assigning the postMetaResolver function to the meta property.

Below is an example of using a postMetaResolver function that fetches the meta tags for a post. This function returns an array of meta tags.

export const postMetaResolver: ResolveFn<MetaTag[]> = (route) => {
const postAttributes = injectActivePostAttributes(route);

return [
{
name: 'description',
content: postAttributes.description,
},
{
name: 'author',
content: 'Analog Team',
},
{
property: 'og:title',
content: postAttributes.title,
},
{
property: 'og:description',
content: postAttributes.description,
},
{
property: 'og:image',
content: postAttributes.coverImage,
},
];
};

The meta tags can be done asynchronously also. Assign the postMetaResolver function to the meta property.

export const routeMeta: RouteMeta = {
title: postTitleResolver,
meta: postMetaResolver,
};

The resolved meta tags can also be accessed in the component using the ActivatedRoute service.

export default class BlogPostComponent {
readonly route = inject(ActivatedRoute);
readonly metaTags$ = this.route.data.pipe(map(data => data['meta']));

// In the template
<my-component [metaTags]="metaTags$ | async"></my-component>
}

Enabling support for Mermaid

Analog's markdown component supports Mermaid. To enable support by the MarkdownComponent define a dynamic import for loadMermaid in withMarkdownRenderer().

withMarkdownRenderer({
loadMermaid: () => import('mermaid'),
});

After it is enabled, Mermaid blocks are transformed by mermaid into SVGs.

Example of mermaid graph:

graph TD
A[Before] -->|Playing with AnalogJS| B(Now Yes !)

Support for Content Subdirectories

Analog also supports subdirectories within your content folder.

The injectContent() function can also be used with an object that contains the route parameter and subdirectory name.

This can be useful if, for instance, you have blog posts, as well as a portfolio of project markdown files to be used on the site.

src/
└── app/
│ └── pages/
│ └── project.[slug].page.ts
└── content/
├── posts/
│ ├── my-first-post.md
│ └── my-second-post.md
└── projects/
├── my-first-project.md
└── my-second-project.md
// /src/app/pages/project.[slug].page.ts
import { injectContent, MarkdownComponent } from '@analogjs/content';
import { AsyncPipe, NgIf } from '@angular/common';
import { Component } from '@angular/core';

export interface ProjectAttributes {
title: string;
slug: string;
description: string;
coverImage: string;
}

@Component({
standalone: true,
imports: [MarkdownComponent, AsyncPipe, NgIf],
template: `
<ng-container *ngIf="project$ | async as project">
<h1>{{ project.attributes.title }}</h1>
<analog-markdown [content]="project.content"></analog-markdown>
</ng-container>
`,
})
export default class ProjectComponent {
readonly project$ = injectContent<ProjectAttributes>({
param: 'slug',
subdirectory: 'projects',
});
}

Loading Custom Content

By default, Analog uses the route params to build the filename for retrieving a content file from the src/content folder. Analog also supports using a custom filename for retrieving content from the src/content folder. This can be useful if, for instance, you have a custom markdown file that you want to load on a page.

The injectContent() function can be used by passing an object that contains the customFilename property.

readonly post$ = injectContent<ProjectAttributes>({
customFilename: 'path/to/custom/file',
});