Redefining Angular Markdown with Analog.js

Analog is still in early days but it’s got a lot going on! Poke around a demo app to explore the features, and learn about its markdown capabilities.


This content originally appeared on Telerik Blogs and was authored by Jonathan Gamble

Analog is still in early days but it’s got a lot going on! Poke around a demo app to explore the features, and learn about its markdown capabilities.

I’ve been intrigued by Analog ever since I saw Brandon Roberts’s video showing him building Angular with Vite. This was the first step to getting the Angular SSR package we really wanted.

The Angular team has stepped up their game incredibly in the last year and a half, but Angular SSR still can’t even get deployed to Vercel or Cloudflare without Analog without problems. I believe this will get fixed eventually when they merge with Wiz, Google’s internal framework, but for now we have Analog.

I built an Analog Todo App with Firebase to show off some new features, but now I am more intrigued with the markdown abilities. Markdown is built-in.

TL;DR

We are going to build a blog with Analog! My version concentrates on the .md files instead of the content directory. You can create markdown files in the pages directory, and it just works. I also added the ability to parse the markdown files in the pages directory for titles and metadata, which does not work out of the box. Analog could be better, but it makes deployment and server handling easy!

Create Our App

First, we need to create our Analog app.

npm create analog@latest

Analog supports the ng update format. If we want to update later, we can use:

ng update @analogjs/platform@latest

Analog uses Vite with Angular, so we can run npm run start or npm run dev or ng serve.

npm run start

Analog Supports Markdown and Front-matter

If you want to create a markdown file, create a .md file with your content, and put the front-matter metadata at the top. This is similar to Nuxt Content. We also need a PostAttribute type. We can put this in models.ts.

export interface PostAttributes {
    title: string;
    slug: string;
    description: string;
    publishedAt: string;
    content: string;
};

Content

Write your articles and put them in the pages/blog folder. I like to separate my regular files from my markdown. I generated some AI articles. My app also uses Tailwind. The slug needs to match the file name. Make sure the markdown renderer is in app.config.ts.

provideContent(withMarkdownRenderer())

App Component

This is your home component, where you can declare your layout. I use prose from Tailwind to define the styles here. I also generate a year for a copyright.

Generally, you want to generate the years and dates on the server and then hydrate the server value to the client; on New Year’s Day your server date may not rehydrate with the same year as the client. I just simplified the code.

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
 
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: `
  <main class="p-3 m-3 prose md:prose-lg lg:prose-xl max-w-none">
    <router-outlet></router-outlet>
     <p>© Rocky Billy {{ year }}</p>
  </main> 
  `,
  styles: [],
})
export class AppComponent {
  readonly year = new Date().getFullYear();
}
 

Resolvers

The key to using Angular SSR is always using resolvers when loading data. If you don’t, your data might not fully load before the app gets rendered. Here are a few helper functions I created for this.

export const injectResolver = <T>(name: string) =>
    inject(ActivatedRoute).data.pipe<T>(map(r => r[name]));
 
export const injectSnapResolver = <T>(name: string) =>
    inject(ActivatedRoute).snapshot.data[name] as T;

This makes injecting the data easier.

Getting the Markdown

Currently, there is no method to get file names outside the content directory. I purged the source code to figure out how to do this. import.meta.glob is the magic Vite function that lets you do this. I plan on doing a PR to make some of this easier later.

function getSlug(filename: string) {
    const parts = filename.match(/^(\\|\/)(.+(\\|\/))*(.+)\.(.+)$/);
    return parts?.length ? parts[4] : '';
}
 
export const indexResolver: ResolveFn<any> = async () => {
 
    const data = import.meta.glob('/src/app/pages/blog/*.md', {
        eager: true,
        import: 'default',
        query: { 'analog-content-list': true },
    });
 
    return Object.keys(data).map((filename) => {
        const attributes = data[filename] as any;
        const slug = attributes['slug'];
 
        return {
            filename: filename.split('/').pop()?.split('.')[0],
            slug: slug ? encodeURI(slug) : encodeURI(getSlug(filename)),
            title: attributes.title
        };
    });
};

Displaying the Blog Pages

We use the previous function and inject it in our resolver to get the names of all the files in our blog folder with an .md extension.

import { Component} from '@angular/core';
import {
  RouterLink,
  RouterOutlet
} from '@angular/router';
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { RouteMeta } from '@analogjs/router';
import { PostAttributes } from '../models';
import { indexResolver, injectSnapResolver } from '../utils';
 
  
export const routeMeta: RouteMeta = {
  resolve: { data: indexResolver }
};
 
@Component({
  standalone: true,
  imports: [RouterOutlet, RouterLink, NgFor, NgIf, AsyncPipe],
  template: `
  <h1>Rocky Billy's Blog</h1>
    <ul>
      <li *ngFor="let post of posts">
        <a [routerLink]="['blog', post.slug]">{{
          post.title
        }}</a>
      </li>
    </ul>
  `,
})
export default class BlogComponent {
  readonly posts = injectSnapResolver<PostAttributes[]>('data');

Notice how easy injecting the resolver is. Just use the routeMeta object along with my custom injectSnapResolver function.

Deployment

So that’s it!

Analog uses Vite, which makes deployment extremely easy. Since my app uses SSR, I decided to host it on Netlify. There are built-in presets for every hosting environment, even Edge Servers.

Repo: GitHub

Demo: Rocky Billy’s Blog

Similar Posts

A few other posts have covered the blog concept, but I want to offer my own.

Future

I love Analog, and I’m super excited about the .ng experimental feature. This allows you to build an Angular component without the boilerplate, and your component will look like a Svelte or Vue file. Analog is still very young, but I plan on helping it grow where I can.


This content originally appeared on Telerik Blogs and was authored by Jonathan Gamble


Print Share Comment Cite Upload Translate Updates
APA

Jonathan Gamble | Sciencx (2024-07-01T09:03:16+00:00) Redefining Angular Markdown with Analog.js. Retrieved from https://www.scien.cx/2024/07/01/redefining-angular-markdown-with-analog-js/

MLA
" » Redefining Angular Markdown with Analog.js." Jonathan Gamble | Sciencx - Monday July 1, 2024, https://www.scien.cx/2024/07/01/redefining-angular-markdown-with-analog-js/
HARVARD
Jonathan Gamble | Sciencx Monday July 1, 2024 » Redefining Angular Markdown with Analog.js., viewed ,<https://www.scien.cx/2024/07/01/redefining-angular-markdown-with-analog-js/>
VANCOUVER
Jonathan Gamble | Sciencx - » Redefining Angular Markdown with Analog.js. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/07/01/redefining-angular-markdown-with-analog-js/
CHICAGO
" » Redefining Angular Markdown with Analog.js." Jonathan Gamble | Sciencx - Accessed . https://www.scien.cx/2024/07/01/redefining-angular-markdown-with-analog-js/
IEEE
" » Redefining Angular Markdown with Analog.js." Jonathan Gamble | Sciencx [Online]. Available: https://www.scien.cx/2024/07/01/redefining-angular-markdown-with-analog-js/. [Accessed: ]
rf:citation
» Redefining Angular Markdown with Analog.js | Jonathan Gamble | Sciencx | https://www.scien.cx/2024/07/01/redefining-angular-markdown-with-analog-js/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.