DEV Community

Connie Leung
Connie Leung

Posted on

Day 28 - Retrieve the Post Author

On day 28, I use the userId of a post to call the users endpoint to retrieve the user name.

In Vue 3, I created another composable to return a user Object with a name property. In Angular 20, it is a User service that uses the experimental httpResource to retrieve a user when the post is updated. In SvelteKit, I added new logic in the load function to query a user using the userId of a post.

Framework Approach
Vue 3 useUser Composable
SvelteKit Make a new request to the users endpoint to retrieve a user by user id
Angular 20 A user httpResource to retrieve a user by user id in a service

Post Types

Create a User type to retrieve id, name.

export type User = {
  id: number;
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

Load User data

Vue 3 application

Create auseUser composable under src/composables folder.

Implement a fetchOne function and declare a user ref to retrieve a user by an ID.

import type { User } from '@/types/user'
import { ref } from 'vue'

export function useUser() {
  const user = ref<User | null>(null)
  const baseUrl = `https://jsonplaceholder.typicode.com/users/${id}`

  function fetchOne(id: number, signal: AbortSignal) {
    return fetch(baseUrl, { signal })
      .then((response) => response.json() as Promise<User>)
      .then((data) => (user.value = data))
  }

  return {
    user,
    fetchOne,
  }
}
Enter fullscreen mode Exit fullscreen mode

The fetchOne function makes a request to the baseUrl to retrieve a user and assign the user touser.value.

Then, the composable returns both user and fetchOne so the Post component can accesss them.

SvelteKit application

I modified the code to use async/await to retrieve a post and the post user.

export type PostWitUser = {
    post: Post | undefined;
    user: User | undefined;
};
Enter fullscreen mode Exit fullscreen mode

PostWithUser is a new type that holds the data of a post and its author.

import { BASE_URL } from '$lib/constants/posts.const';
import type { Post } from '$lib/types/post';
import type { PostWitUser, User } from '$lib/types/user';
import type { PageLoad } from './$types';

// retreive a post by an ID
export const load: PageLoad = async ({ params, fetch }): Promise<PostWitUser> => {
    console.log('params', params);

    try {
        const response = await fetch(`${BASE_URL}/posts/${params.id}`);
        const post = (await response.json()) as Post;

        const userResponse = post ? await fetch(`${BASE_URL}/users/${post?.userId}`) : undefined;
        const user = userResponse ? ((await userResponse.json()) as User) : undefined;

        return {
            post,
            user
        };
    } catch (error) {
        console.error('Error fetching a post:', error);
        return {
            post: undefined,
            user: undefined
        };
    }
};
Enter fullscreen mode Exit fullscreen mode

When the first request retrieves a post successfully, the second request uses the userId to retrieve the user.

Angular 20 application

Install zod to parse the result of the httpResource.

npm install --save-exact zod
Enter fullscreen mode Exit fullscreen mode
import z from 'zod';

export const userSchema = z.object({
    id: z.number(),
    name: z.string(),
});

export type User = z.infer<typeof userSchema>;
Enter fullscreen mode Exit fullscreen mode

Create a userSchema and infer the User type.

import { httpResource } from '@angular/common/http';
import { Injectable, Signal } from '@angular/core';
import { Post } from '../types/post.type';
import { User, userSchema } from '../types/user.type';

const BASE_URL = 'https://jsonplaceholder.typicode.com/users';

@Injectable({
  providedIn: 'root',
})
export class UserService {
  createUserResource(post: Signal<Post | undefined>) {
    return httpResource<User>(
      () => {
        const result = post();
        return result ? `${BASE_URL}/${result.userId}` : undefined;
      },
      {
        defaultValue: undefined,
        equal: (a, b) => a?.id === b?.id,
        parse: userSchema.parse,
      },
    ).asReadonly();
  }
}
Enter fullscreen mode Exit fullscreen mode

This is a technique to create an httpResource in a service. The createUserResource method accepts a post signal to track its changes. When the post is updated, the httpResource makes an HTTP request to retrieve a user the user id.

{
    defaultValue: undefined,
    equal: (a, b) => a?.id === b?.id,
    parse: userSchema.parse,
}
Enter fullscreen mode Exit fullscreen mode

The default value is undefined. Two users are considered equal when they have the same ID and no HTTP request. userSchema.parse parses the raw data to a User object.

Replace the User mock data with a fetch call

Vue 3 application

In the Post view, import the usePost composable and destructure fetch and user.

In the Post component, import usePost and invoke fetchOne to retrieve a post by an ID.

import { useRoute } from 'vue-router'
import { usePost } from '@/composables/usePost'
import { useUser } from '@/composables/useUser'

const { post, fetchOne } = usePost()
const { user, fetchOne: fetchUser } = useUser()

const { params } = useRoute();
fetchOne(+params.id)
fetchUser(post?.userId)
Enter fullscreen mode Exit fullscreen mode
<template>
  <div v-if="isLoading" class="text-center my-10">
    Loading...
  </div>
  <div v-if="post && user" class="mb-10">
    <h1 class="text-3xl">{{ post.title }}</h1>
    <div class="text-gray-500 mb-10">by {{ user.name }}</div>
    <div class="mb-10">{{ post.body }}</div>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

In the template, replace the hardcoded name with {{ user.name }}.

SvelteKit application

<script lang="ts">
    import type { PageProps } from './$types';

    const { data }: PageProps = $props();
    const { post, user } = data;
</script>
Enter fullscreen mode Exit fullscreen mode

In posts/[id]/+page.svelte, the load function retrieves a post by an ID and the user by the post's userId.

<div class="mb-10">
    <h1 class="text-3xl">{ post?.title }</h1>
    <div class="text-gray-500 mb-10">by { user?.name }</div>
    <div class="mb-10">{ post?.body }</div>
</div>
Enter fullscreen mode Exit fullscreen mode

Now, the template can display the user name by interpolating { user?.name }.

Angular 20 application

import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
import { UserService } from './services/user.service';
import { Post } from './types/post.type';

@Component({
  ... no change ...
})
export default class PostComponent {
  readonly userService = inject(UserService);

  post = input<Post>();

  userRef = this.userService.createUserResource(this.post);

  user = computed(() => (this.userRef.hasValue() ? this.userRef.value() : undefined));
}
Enter fullscreen mode Exit fullscreen mode

The PostComponent injects the userService and calls the createUserResource method to create a userRef.

user is a computed signal that computes the user value. When the userRef has value, this.userRef.value() returns a valid User object. Otherwise, user gets undefined.

No change to the inline template.

Fix the User bug (Vue 3 only)

At this point, there is a bug in the Post view where the user is undefined. It happens when fetchUser(post?.userId) is invoked before the post data arrives. The fix is to use watch to track changes to the post ref. When the ref receives a new value, the watcher callback calls the fetchUser function to retrieve the user name.

watch(() => ({ ...post.value }), (newPost, oldPost, onCleanup) => {
  const controller = new AbortController()

  if (newPost?.userId) {
    fetchUser(newPost.userId, controller.signal)
  }

  onCleanup(() => {
    controller.abort()
  })
})
Enter fullscreen mode Exit fullscreen mode

Pass a getter function to watch to track updates to the post. When the new post has a user id, it is used to fetch a new user to be displayed.

We have successfully displayed the user of the post.

Github Repositories

Resources

Top comments (0)