3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 5

Fetching Data from the Reddit API

Utilising HTTP request streams

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 a Photo object for example, and we have an interface for that so we can enforce that it 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 and add the following:

/* eslint-disable @typescript-eslint/naming-convention */
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

Modify the root module of your application to include the HttpClientModule and inject HttpClient into your RedditService

Modify the methods in the RedditService to reflect the following:

  constructor(private http: HttpClient) {}

  getGifs() {
    return this.fetchFromReddit('gifs');
  }

  private fetchFromReddit(subreddit: string) {
    return this.http
      .get<RedditResponse>(
        `https://www.reddit.com/r/${subreddit}/hot/.json?limit=100`
      )
      .pipe(
        // If there is an error, just return an empty observable
        // This prevents the stream from breaking
        catchError(() => EMPTY),

        // Convert response into the gif format we need
        map((res) => this.convertRedditPostsToGifs(res.data.children))
      );
  }

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

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

Our request to Reddit is going to become quite complex, so we have separated it out into a separate fetchFromReddit method that accepts the subreddit we want to fetch GIFs from as a parameter. Since we are dealing with an HTTP request, we have to consider that it might fail. We are dealing with this in a simple way here with catchError. If there is an error, we will catch it and return the EMPTY stream instead which will stop the stream from breaking entirely. Basically, this strategy just ignores errors as if they never happened - if a user makes a request to a subreddit that does not exist, they will just get no results.

We also map the response we get from our HTTP request because we don’t want all of the raw data - we want to convert it into our specific format for a RedditPost. To do this, we will first pass it to the convertRedditPostsToGifs method which will handle most of the data formatting. There is another tricky aspect here though, and that is that there are multiple different types of media that might be used for a “GIF”. Our getBestSrcForGif method will handle searching through the data and returning the best available src for our video tag.

Let’s finish implementing those methods now.

Modify the convertRedditPostsToGifs method to reflect the following:

  private convertRedditPostsToGifs(posts: RedditPost[]): Gif[] {
    return posts
      .map((post) => ({
        src: this.getBestSrcForGif(post),
        author: post.data.author,
        name: post.data.name,
        permalink: post.data.permalink,
        title: post.data.title,
        thumbnail: post.data.thumbnail,
        comments: post.data.num_comments,
        loading: false,
      }))
      .filter((gifs) => gifs.src !== null);
  }
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).