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.
- How to Build a Blog with Analog – This is Brandon’s official post, which focuses on the
content
directory. - Create a Blog with Analog and Angular – This version focuses on SEO and prerendering.
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
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/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.