3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 5

Fetching Data from the Reddit API

STANDARD

Fetching Data from the Reddit API

Eventually we are going to implement a rather complex stream to deal with fetching data from Reddit — this will include pagination and automatic retries to fetch more content if we didn’t find enough GIFs. But, to begin with, we are going to keep things simple. We are just going to make a single request to the Reddit API to retrieve data and we will save paging until the next lesson.

Create the Interfaces

We like to know what data is available in our objects. We have used this multiple times now where we have an interface so that we can enforce that something has certain properties. This way, TypeScript will warn us when we have done something wrong and we can also get our data auto completing in the template.

We are about to pull in some data from Reddit. We have our Gif interface already, but that is the target object we want to create from the data we pull in, the data that Reddit returns is a bit different.

We could just pull in the data from Reddit, give it an any type, and not worry about it. But let’s say we then access some data like:

data.title

If we just give our data any type then it will assume the value is valid, regardless of whether or not that property exists. So, first, we are going to create some interfaces that define the “shape” of the data returned from Reddit.

Create an interface file called reddit-post.ts in your interfaces folder and add the following:

export interface RedditPost {
  data: RedditPostData;
}

interface RedditPostData {
  author: string;
  name: string;
  permalink: string;
  preview: RedditPreview;
  secure_media: RedditMedia;
  title: string;
  media: RedditMedia;
  url: string;
  thumbnail: string;
  num_comments: number;
}

interface RedditPreview {
  reddit_video_preview: RedditVideoPreview;
}

interface RedditVideoPreview {
  is_gif: boolean;
  fallback_url: string;
}

interface RedditMedia {
  reddit_video: RedditVideoPreview;
}

We really just want one interface called RedditPost here, but that data is an object that contains multiple children. Some of those children also contain additional objects. What we need to do is inspect the response returned from making a request to the Reddit API:

https://www.reddit.com/r/gifs/hot/.json?limit=100

And for all the simple values like strings and numbers we can just add directly to our interface. But, when we run into another object, we need to create a separate interface that is then used in our parent interface. This is why we end up with such a complex structure that in a general sense looks like this:

RedditPost {
    RedditPostData {
        RedditMedia {}
        RedditPreview {
            RedditVideoPreview
        }
    }
}

We drill down through the structure returned from the API and map it out in our interface.

Create another interface in a file called reddit-response.ts:

import { RedditPost } from './reddit-post';

export interface RedditResponse {
  data: RedditResponseData;
}

interface RedditResponseData {
  children: RedditPost[];
}

The RedditPost data we want is not the only data that is returned from Reddit, but it is all we are interested in. The purpose of this interface is to be a container for the response returned from Reddit.

Export the new interfaces from the index.ts file:

export * from './gif';
export * from './reddit-post';
export * from './reddit-response';

Making a real HTTP request

Before we can make HTTP requests, we need to add the HttpClientModule to our application. See if you can remember how to do that, but I will also post the solution below.

Modify your app.config.ts file to reflect the following:

import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideHttpClient } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideAnimationsAsync(),
    provideHttpClient(),
  ],
};

NOTE: The provideAnimationsAsync here was added automatically when we used the ng add schematic for Angular Material

Modify the methods in the RedditService to reflect the following:

import { Injectable, computed, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
import { Gif, RedditPost, RedditResponse } from '../interfaces';
import { EMPTY, catchError, map } from 'rxjs';

export interface GifsState {
  gifs: Gif[];
}

@Injectable({ providedIn: 'root' })
export class RedditService {
  private http = inject(HttpClient);

  // state
  private state = signal<GifsState>({
    gifs: [],
  });

  // selectors
  gifs = computed(() => this.state().gifs);

  //sources
  private gifsLoaded$ = this.fetchFromReddit('gifs');

  constructor() {
    //reducers
    this.gifsLoaded$.pipe(takeUntilDestroyed()).subscribe((gifs) =>
      this.state.update((state) => ({
        ...state,
        gifs: [...state.gifs, ...gifs],
      }))
    );
  }

  private fetchFromReddit(subreddit: string) {
    return this.http
      .get<RedditResponse>(
        `https://www.reddit.com/r/${subreddit}/hot/.json?limit=100`
      )
      .pipe(
        catchError((err) => EMPTY),
        map((response) => this.convertRedditPostsToGifs(response.data.children))
      );
  }

  private convertRedditPostsToGifs(posts: RedditPost[]) {
    // TODO: Implement
  }

  private getBestSrcForGif(post: RedditPost) {
    // TODO: Implement
  }
}
STANDARD
Key

Thanks for checking out the preview of this lesson!

You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).