Feature: update injectContent function to return content object instead of string

This issue has been tracked since 2023-01-20.

Which scope/s are relevant/related to the feature request?

content

Information

Currently if you want to display a single post, you can use the injectContent() function to get the rendered markdown as a string. If you want to display the metadata from the content within the component, you basically have to reimplement injectContent() to return the content with metadata.

import { AsyncPipe, DatePipe, NgIf } from '@angular/common';
import { Component } from '@angular/core';
import { MarkdownComponent } from '@analogjs/content';
import { ContentFile, injectContentFiles } from '@analogjs/content';
import { injectActivatedRoute } from '@analogjs/router';

export function injectPost(slug: string) {
  const route = injectActivatedRoute();
  return injectContentFiles<Post>().find(
    (posts) =>
      posts.filename === `/src/content/${route.snapshot.paramMap.get(slug)}.md`
  );
}

@Component({
  selector: 'post',
  standalone: true,
  imports: [MarkdownComponent, AsyncPipe, NgIf, DatePipe, ReadingTimePipe],
  template: `
    <div class="flex flex-grow justify-center min-h-screen" *ngIf="post">
      <article class="w-screen max-w-4xl p-8">
        <h2 class="text-gray-600 text-2xl">{{ post.attributes.title }}</h2>

        <span class="font-light text-sm">
          {{ post.attributes.publishedDate | date : 'MMMM dd, yyyy' }} -
          {{ post.content | readingtime }} min read
        </span>

        <analog-markdown [content]="post.content"></analog-markdown>
      </article>
    </div>
  `,
})
export default class BlogPostComponent {
  post = injectPost('slug');
}

The proposed change would return the content with metadata instead of the string itself

import { AsyncPipe, DatePipe, NgIf } from '@angular/common';
import { Component } from '@angular/core';
import { MarkdownComponent, injectContent } from '@analogjs/content';

export interface Post {
  title: string;
  slug: string;
  published: boolean;
  publishedDate: string;
}

@Component({
  selector: 'post',
  standalone: true,
  imports: [MarkdownComponent, AsyncPipe, NgIf, DatePipe, ReadingTimePipe],
  template: `
    <div class="flex flex-grow justify-center min-h-screen" *ngIf="post">
      <article class="w-screen max-w-4xl p-8">
        <h2 class="text-gray-600 text-2xl">{{ post.attributes.title }}</h2>

        <span class="font-light text-sm">
          {{ post.attributes.publishedDate | date : 'MMMM dd, yyyy' }} -
          {{ post.content | readingtime }} min read
        </span>

        <analog-markdown [content]="post.content"></analog-markdown>
      </article>
    </div>
  `,
})
export default class BlogPostComponent {
  post = injectContent<Post>();
}

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No
goetzrobin wrote this answer on 2023-01-25

@brandonroberts just ran into the same issue working on my personal website! I can work on this if you want

brandonroberts wrote this answer on 2023-01-25

That would be great 👍

goetzrobin wrote this answer on 2023-01-25

Awesome! What do you think we should do when no file is found for the current route?
Currently the customFallback param is returned.
When returning an object we have other options:

  1. Returning an optional ContentFile inside the Observable & deprecating the fallback param
export function injectContent<
  Attributes extends Record<string, any> = Record<string, any>
>(
  param = 'slug',
  fallback = 'No Content Found'
): Observable<ContentFile<Attributes> | undefined> {
  const route = inject(ActivatedRoute);
  const contentFiles = injectContentFiles<Attributes>();
  return route.paramMap.pipe(
    map((params) => params.get(param)),
    map((slug) => {
      return contentFiles.find(
        (file) => file.filename === `/src/content/${slug}.md`
      );
    })
  );
}
  1. Building a 'not found' ContentFile that returns the customFallback as its content
export function injectContent<
  Attributes extends Record<string, any> = Record<string, any>
>(
  param = 'slug',
  fallback = 'No Content Found'
): Observable<ContentFile<Attributes | Record<string, never>>> {
  const route = inject(ActivatedRoute);
  const contentFiles = injectContentFiles<Attributes | Record<string, never>>();
  return route.paramMap.pipe(
    map((params) => params.get(param)),
    map((slug) => {
      return (
        contentFiles.find(
          (file) => file.filename === `/src/content/${slug}.md`
        ) || {
          attributes: {},
          filename: '',
          content: fallback,
        }
      );
    })
  );
}

Let me know what you think 👍

brandonroberts wrote this answer on 2023-01-25

Option 2 is good to me

More Details About Repo
Owner Name analogjs
Repo Name analog
Full Name analogjs/analog
Language TypeScript
Created Date 2022-07-06
Updated Date 2023-03-28
Star Count 885
Watcher Count 18
Fork Count 67
Issue Count 33

YOU MAY BE INTERESTED

Issue Title Created Date Updated Date