Telerik blogs

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.


About the Author

Jonathan Gamble

Jonathan Gamble has been an avid web programmer for more than 20 years. He has been building web applications as a hobby since he was 16 years old, and he received a post-bachelor’s in Computer Science from Oregon State. His real passions are language learning and playing rock piano, but he never gets away from coding. Read more from him at https://code.build/.

 

 

Related Posts

Comments

Comments are disabled in preview mode.