3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 7

Modify Streams with User Input

Allowing the user to change subreddits

STANDARD

Modify Streams with User Input

Before we get into the next addition to our stream, let’s briefly recap what our gifsLoaded$ stream does so far. This is the general flow:

  1. Gets a stream of values from the pagination$ stream
  2. It will start that stream with one single emission of null automatically
  3. It will take those emissions and then switch to the fetchFromReddit stream, but we use concatMap because we want to wait for each request to Reddit to complete before addressing the next emission

The key thing we are going to address this lesson is that, at the moment, the subreddit we are using to pull in GIFs is hardcoded to the gifs subreddit. We want our user to be able to supply whatever subreddit they like to pull in GIFs from.

This means that we want to achieve the following:

  1. The user should be able to type a subreddit into a form control
  2. The gifsLoaded$ stream will now emit GIFs from that particular subreddit (and it will get rid of any GIFs from the previous subreddit)

The question is, how do we modify gifsLoaded$ to support this behavior?

Adding a subredditChanged$ Source

Now we want to react to the user changing the subreddit, so we are dealing with a new source of data now and we are going to have to add that to our service.

It can be hard to figure out what that source of data should be exactly. Your first instinct, and one that would technically work, is to just create a new source that is a Subject:

subredditChanged$ = new Subject<string>();

You could just next this with whatever subreddit you want to change to, and react to that by incorporating it into the gifsLoaded$ source.

This faces the same problem as our imperative approach to the videoLoadComplete$ source. It will work, but it requires an imperative call to make it work and we have no idea what subredditChanged$ is just by looking at it. Again, sometimes it might make sense to do that, but it is worth first considering what exactly our data source is and if we can just access that directly.

We are going to be reacting to a user entering data into a form. That form will be associated with a Form Control. What we really want to do is react to the value of that control changing.

The good news is that we can just define that form control directly in our service. This will give the service direct access to the values as they change, and we can also bind the form in our template to the form control in the service.

Add a FormControl to the RedditService:

export class RedditService {
  private http = inject(HttpClient);
  subredditFormControl = new FormControl();

A FormControl has a valueChanges property which is an observable stream that will emit every time its value changes — just what we need!

Add the following source to the RedditService:

  private subredditChanged$ = this.subredditFormControl.valueChanges.pipe(
    debounceTime(300),
    distinctUntilChanged(),
    startWith('gifs'),
    map((subreddit) => (subreddit.length ? subreddit : 'gifs'))
  );

We’re using valueChanges from the form control to create our source here… but what is all this other stuff?

Consider that we want to launch a request to the Reddit API every time the value in that form control changes. If I type chemicalreactiongifs that means the form control will change 20 times (one for each letter) and 20 requests to Reddit will be launched — really, we only want to launch one request when the user is done typing.

So, we use debounceTime which will cause the stream not to emit any values until it has received no new value for 300ms. This might still cause unnecessary requests if the user types slowly or if they pause in the middle, but it should remove a lot of unnecessary requests. We also use distinctUntilChanged which will cause the stream not to emit a value if the new value is the same as the old value (no need to re-fetch the same data). We also make use of startWith because, just like before, we still want to launch an initial request for the gifs subreddit without waiting for the user to actually type something.

Finally, we also want to handle the situation where the user clears the search bar such that there is no search term — in this case we want to default back to using the gifs value so we map to that if the search term is empty.

Reacting to the subreddit changing

Now that we have our source, we can incorporate it into our gifsLoaded$ stream so that the gifsLoaded$ source reacts to the subreddit changing by emitting gifs for that subreddit.

Update the gifsLoaded$ source to reflect the following:

  //sources
  pagination$ = new Subject<string | null>();

  private subredditChanged$ = this.subredditFormControl.valueChanges.pipe(
    debounceTime(300),
    distinctUntilChanged(),
    startWith('gifs'),
    map((subreddit) => (subreddit.length ? subreddit : 'gifs'))
  );

  private gifsLoaded$ = this.subredditChanged$.pipe(
    switchMap((subreddit) =>
      this.pagination$.pipe(
        startWith(null),
        concatMap((lastKnownGif) =>
          this.fetchFromReddit(subreddit, lastKnownGif, 20)
        )
      )
    )
  );
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).