DEV Community

Connie Leung
Connie Leung

Posted on

Day 18 - Github Card project Part 1 - Data Retrieval

Day 18 - Github Card project Part 1 - Data Retrieval

Table of Contents

Component Fundamentals with JavaScript Frameworks

On day 18, I started working on the Github User Profile exercise in Vue 3, Angular 19 and Svelte 5.

My CSS skill is not strong, so I will follow the advice of the instructor and use DaisyUI to style the card. I leave styling last and concentrate on data retrieval, and parent-child communication

This small project will split into three parts

  • Part 1: Data Retrieval
  • Part 2: Parent-Child communication
  • Part 3: Styling and template

Let's start with data retrieval and you would be surprise that the three frameworks has different approaches to call the API to retrieve data.

Vue 3 - Composable.
Angular 20 - Use experimental httpResource because I want to live dangerously.
Svelte 5 - Data loader in page.ts

Create a Github Personal Token

Before retrieving a Github profile by user name, we must create a Github personal access token

  • Click the Avartar menu to open the dropdown
  • Select Settings
  • Click Developer Settings
  • Expand Personal access tokens and click Fine-grained tokens to create a token

Define the Github environment variable

Vue 3 and Svelte 5 use Vite, so it is easy to create environment variables in them, unlike Angular.

  • Create an .env-example file
VITE_GITHUB_TOKEN=<github personal access token>
Enter fullscreen mode Exit fullscreen mode

Create an environment variable to store the Github personal access token. The variable must begin with a VITE prefix.

  • Create an .env file
VITE_GITHUB_TOKEN=github_xxx
Enter fullscreen mode Exit fullscreen mode

Create GithubProfile type

export type GithubProfile = {
    login: string;
    name: string;
    followers: number;
    following: number;
    html_url: string;
    avatar_url: string;
    bio: string | null;
    public_repos: number;
}
Enter fullscreen mode Exit fullscreen mode

The GithubProfile type is the same for all three applications.

A Github profile has username, name, number of followers, number of following, html URL, avatar ULR, a nullable bio and number of public repositories.

Data Retrieval

Vue 3 application

Create the Github Profile Composable

  • Create a composables folder under src
  • Under composables, create a useGithubProfile.ts composable to retrieve a Github profile by an username.

This new composable defines three ref to store the username, the profile and the error message respectively.

export function useGithubProfile() {
    ... composable logic ...
}
Enter fullscreen mode Exit fullscreen mode
    const username = ref('')
    const profile = ref<GithubProfile | null>(null)
    const error = ref('');
Enter fullscreen mode Exit fullscreen mode

The composable watches the username ref in watch and makes a fetch call to retrieve the profile.

watch(username, async (newUserName) => {
    if (!newUserName) {
        profile.value = null;
        error.value = '';
        return;
    }

    profile.value = null;
    error.value = '';

    fetch(`https://api.github.com/users/${newUserName}`, {
        headers: {
            Authorization: `Bearer ${import.meta.env.VITE_GITHUB_TOKEN}`,
        }
    })
    .then( async response => {
        if (!response.ok) {
            error.value = 'Network response was not ok.'
        }
        profile.value = await response.json() as GithubProfile;
    })
    .catch(err => {
        console.error('There has been a problem with your fetch operation:', err);
        if (err instanceof Error) {
            error.value = err.message
        }
        error.value = 'Failed to fetch profile.'
    });
});
Enter fullscreen mode Exit fullscreen mode

The watch callback resets the profile and error refs. Fetch calls the Github API to retrieve the user profile. The Authorization header stores the personal github token as a bearer token.

When the HTTP request is successful, the profile ref is updated to the JSON response. When an HTTP error occurs, the error ref stores the error message.

    return {
        username,
        profile,
        error,
    }
}
Enter fullscreen mode Exit fullscreen mode

Finally, the composable returns the refs so that they are accessible in components.

SvelteKit application

Loading Data

I created a routes/+page.ts file to load data from a hardcoded list of github usernames. The TypeScript file has a load function that iterates the usernames, fetch the profiles and return them in a list.

export const load: PageLoad = async () => {
    const usernames = ['johnsoncodehk', 'antfu', 'railsstudent', 'danielkellyio', 'hootlex', 'MooseSaeed'];

    const profilesSettled = await Promise.allSettled(
        usernames.map((username) => fetchGithubProfile(username))
    );

    const profiles = profilesSettled.reduce((acc, result, idx) => {
        if (result.status === 'fulfilled') {
            return acc.concat({
                key: idx,
                profile: result.value,
            });
        } else {
            return acc.concat({
                key: idx,
                error: `Error fetching profile: ${result.reason}`
            });

        }
    }, [] as GithubProfileItem[])

    return {
        data: profiles
    };
};
Enter fullscreen mode Exit fullscreen mode

The Promise.allSettled ensures that fetch calls always complete regardless success or failure. When the request is successful, the list stores the unique key and profile. Otherwise, the list element consists of the unique key and an errormessage.

The unique key is required for rendering the profiles in a list in the Github Profile List component.

The fetchGithubProfile function is similar to the fetch call in the Vue composable.

export function fetchGithubProfile(username: string, ): Promise<GithubProfile> {
    const url = `https://api.github.com/users/${username}`;
    return fetch(url, {
        headers: {
            Authorization: `Bearer ${import.meta.env.VITE_GITHUB_TOKEN}`,
        }
    })
    .then(async (response) => {
        if (!response.ok) {
            throw new Error(`GitHub profile not found for user: ${username}`);
        }

        const result = await response.json() as Promise<GithubProfile>;
        return result;
    })
    .catch((error) => {
        console.error('Error fetching GitHub profile:', error);
        throw error;
    });
}
Enter fullscreen mode Exit fullscreen mode

When the HTTP response is successful, the function awaits the JSON response and return the JSON data. When the HTTP response fails, the function logs the error and throws the error message.

Angular 20 application

Define Github Token Build Variable

In angular.json, define GITHUB_TOKEN variable.

"define": {
    "GITHUB_TOKEN": "'secret value'"
}
Enter fullscreen mode Exit fullscreen mode

secret_value is dummy value, and it will be overridden on command line.

Add src/types.d.ts and declare GITHUB_TOKEN constant

declare const GITHUB_TOKEN: string;
Enter fullscreen mode Exit fullscreen mode
export GITHUB_TOKEN=<github personal token>
ng build --define GITHUB_TOKEN=\'$GITHUB_TOKEN\'
serve dist
Enter fullscreen mode Exit fullscreen mode

Navigate to http://localhost:3000 to launch the Angular application.

Moreover, add output_path so the files are compiled into dist folder instead of dist/angular-github/profile subfolder.

"outputPath": {
  "base": "dist",
  "browser": ""
}
Enter fullscreen mode Exit fullscreen mode

Provide HttpClient and fetch feature

// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withFetch()),
  ]
};
Enter fullscreen mode Exit fullscreen mode

In order to use httpResource in an Angular appliction, the providers array of the app config must have the provideHttpClient provider. HttpClient uses XmlHttpRequest by default, I override it with the withFetch feature to use the Fetch API. It is to mimic the behavior of the Vue 3 and Svelte 5 demos.

Define httpResource in GithubProfileCardComponent

The easiest way to define an httpResource is within an Angular Component. Therefore, I create a new GithubProfileCardComponent.

@Component({
    selector: 'app-github-profile-card',
    template: `Working`,
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GithubProfileCardComponent {}
Enter fullscreen mode Exit fullscreen mode

The component currently displays a static text because it has no data.

username = input.required<string>();

profileResource = httpResource<GithubProfile>(() => this.username() ? { 
    url: `https://api.github.com/users/${this.username()}`,
        method: 'GET',
        headers: {
            Authorization: `Bearer ${GITHUB_TOKEN}`
        }
    }: undefined, {
        equal: (a, b) => a?.login === b?.login,
    }
);
Enter fullscreen mode Exit fullscreen mode

username is an input signal that holds the Github user name.

username is reactive. When the user name changes, the profileResource makes a HTTP request to retrieve the profile resource.

{
    equal: (a, b) => a?.login === b?.login,
}
Enter fullscreen mode Exit fullscreen mode

When two profiles have the same login, the profileResource does not cause change detection and the template does not rerender.

profile = computed(() => this.profileResource.hasValue() ?this.profileResource.value() : undefined);

error = computed(() => this.profileResource.error()?.message || '');
Enter fullscreen mode Exit fullscreen mode

profile is a computed signal that derives the value of the Github profile. It displays either the resource value of undefined.

error computed signal displays either the error message or an empty string.

We have successfully retrieved the Github profiles in three frameworks.

Github Repositories

Github Pages

Resources

Top comments (0)