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.
Click here to reveal solution
Solution
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
}
}