Build a YouTube video search app with Angular and RxJS
Tags: angular, rxjs, tutorial
Published: 2020-07-28 14:34:03

---
title: Build a YouTube video search app with Angular and RxJS
published: true
date: 2020-07-20 16:00:51 UTC
tags: angular,rxjs,tutorial
canonical_url: https://blog.logrocket.com/build-a-youtube-video-search-app-with-angular-and-rxjs/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/k0kirllnqaafaojpftte.png
---
**Written by [Godson Obielum](https://blog.logrocket.com/author/godsonobielum/)**✏️

In this tutorial, we’ll demonstrate how to build a YouTube video search application using Angular and RxJS. We’ll do this by building a single-page application that retrieves a list of videos from YouTube by passing in a search query and other parameters to the YouTube search API.

We’ll use the following tools to build our app.

- [TypeScript](https://www.typescriptlang.org/), a typed superset of JavaScript that compiles to plain JavaScript and provides type capabilities to JavaScript code
- [Angular](https://angular.io/), a JavaScript framework that allows you to create efficient and sophisticated single-page applications
- [RxJS](https://rxjs-dev.firebaseapp.com/), a library for composing asynchronous and event-based programs by using observable sequences. Think of RxJS as Lodash but for events

You should have a basic understanding of how these tools work to follow along with this tutorial. We’ll walk through how to use these tools together to build a real-world application. As you go along, you’ll gain practical insight into the core concepts and features they provide.

You can find the final code in this [GitHub repository](https://github.com/devgson/angular-youtube-search).

## Prerequisites

You’ll need to have certain libraries installed to build this project locally. Ensure you have the [Node package](https://nodejs.org/en/download/) installed.

We’ll use [Angular CLI v6.0.0](https://angular.io/cli#installing-angular-cli) to generate the project, so you should ideally have that version installed to avoid weird errors later on.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Project setup

### 1. Structure the application

Before we start writing code, let’s conceptualize the features to be implemented in the application and determine the necessary components we’ll need.

We’ll keep it as simple as possible. At the core, we’ll need to have an input element that allows the user to type in a search query. That value will get sent to a service that uses it to construct a URL and communicate with YouTube’s search API. If the call is successful, it will return a list of videos that we can then render on the page.

We can have three core components and one service: a component called `search-input` for the input element, a component called `search-list` for rendering the list of videos, and a parent component called `search-container` that renders both the `search-input` and `search-list` components.

Then we’ll have a service called `search.service`. You could think of a service as the [data access layer (DAL)](https://en.wikipedia.org/wiki/Data_access_layer), that’s where we’ll implement all the relevant functionality that’ll enable us to communicate with the YouTube search API and handle the subsequent response.

In summary, there will be three components:

- `search-container`
- `search-input`
- `search-list`

The `search-input` and `search-list` components will be stateless while `search-container` will be stateful. [Stateless](https://ultimatecourses.com/blog/stateful-stateless-components#stateful) means the component never directly mutates state, while stateful means that it stores information in memory about the app state and has the ability to directly change/mutate it.

Our app will also include one service:

- `search.service`

Now let’s dive into the technical aspects and set up the environment.

### 2. Set up the YouTube search API

We’ll need to get a list of YouTube videos based on whatever value is typed into the input element. Thankfully, YouTube provides a way that allows us to do exactly that by using the YouTube search API. To gain access to the API, you’ll need to register for an API token.

First, if you don’t already have one, you’ll need to [sign up for a Google account](https://www.google.com/accounts/NewAccount). When that’s done, head over to the Google [developer console](https://console.developers.google.com/?pli=1) to create a new project.

![YouTube Search API — Create Project](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/07/youtube-search-api-create-project.png?resize=720%2C345&ssl=1)

Once the project is successfully created, follow the steps below to get an API token.

1. Navigate to the credentials page by clicking on `Credentials` located on the sidebar menu
2. Click on the `+ CREATE CREDENTIALS` button located at the top of the page and select `API key.` A new API key should be created. Copy that key and store it somewhere safe (we’ll come back to it shortly)
3. Head over to the API and Services page by clicking on `APIs & Services` located at the top of the sidebar
4. Click on `ENABLE APIs AND SERVICES` at the top of the page. You’ll be redirected to a new page. Search for the YouTube Data API and click on the `Youtube Data API v3` option. Once again, you’ll be redirected to another page. Click `Enable` to allow access to that API

With that done, we can start building out the application and the necessary components.

### 3. Scaffold the application

Create a directory for the application. From your terminal, head over to a suitable location on your system and issue in the following commands.

```jsx
# generate a new Angular project
ng new youtube-search   `

# move into it 
cd youtube-search
```

This uses the Angular CLI to generate a new project called `youtube-search`. There’s no need to run `npm install` since it automatically installs all the necessary packages and sets up a reasonable structure.

Throughout this tutorial, we’ll use the Angular CLI to create our components, service, and all other necessary files.

## Building the application

### 1. Set up the search service

Before we build the search service, let’s create the folder structure. We’ll set up a `shared` module that will contain all the necessary services, models, etc.

Make sure you’re in your project directory and navigate to the `app` folder by running the following command.

```jsx
cd src/app
```

Create a new module called `shared` by running the following command in the terminal.

```jsx
ng generate module shared
```

This should create a new folder called `shared` with a `shared.module.ts` file in it.

Now that we have our module set up, let’s create our service in the `shared` folder. Run the following command in the terminal.

```jsx
ng generate service shared/services/search
```

This should create a `search.service.ts` file in the `shared/services` folder.

Paste the following code into the `search.service.ts` file. We’ll examine each chunk of code independently.

```jsx
// search.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class SearchService {

  private API_URL = 'https://www.googleapis.com/youtube/v3/search';
  private API_TOKEN = 'YOUR_API_TOKEN';

  constructor(private http: HttpClient) {}

  getVideos(query: string): Observable <any> {
    const url = `${this.API_URL}?q=${query}&key=${this.API_TOKEN}&part=snippet&type=video&maxResults=10`;
    return this.http.get(url)
      .pipe(
        map((response: any) => response.items)
      );
  }
}
```

First, take a look at the chunk of code below.

```jsx
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})

[...]
```

In the first part of the code, we simply import the necessary files that’ll help us build our service. `map` is an RxJS operator that is used to modify the response received from the API call. `HttpClient` provides the necessary HTTP methods.

`@Injectable()` is a decorator provided by Angular that marks the class located directly below it as a service that can be injected. `{ providedIn: 'root'}` signifies that the service is provided in the root component of the Angular app, which in this case is the `app` component.

Let’s look at the next chunk:

```jsx
[...]

export class SearchService {

  private API_URL = 'https://www.googleapis.com/youtube/v3/search';
  private API_TOKEN = 'YOUR_API_KEY';

  constructor(private http: HttpClient) {}

  getVideos(query: string): Observable <any> {
    const url = `${this.API_URL}?q=${query}&key=${this.API_KEY}&part=snippet&type=video&maxResults=10`;
    return this.http.get(url)
      .pipe(
        map((response: any) => response.items)
      );
  }
}
```

We have two private variables here. Replace the value of `API_KEY` with the API token you got when you created a new credential.

Finally, the `getVideos` method receives a search query string passed in from the input component, which we’ve yet to create. It then uses the `http` get method to send off a request to the URL constructed. It returns a response that we handle with the `map` operator. The list of YouTube video details is expected to be located in the `response.items` object and, since we’re just interested in that, we can choose to return it and discard the other parts.

Due to the fact that the search service uses the HTTP client, we have to import the HTTP module into the root component where the service is provided. Head over to the `app.module.ts` file located in the `app` folder and paste in the following code.

```jsx
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    HttpClientModule,
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

That’s basically all for the search service. We’ll make use of it soon.

### 2. Add a video interface file

Let’s quickly set up an `interface` file. A TypeScript interface allows us to define the syntax to which any entity must adhere. In this case, we want to define certain properties each video object retrieved from the Youtube search API should contain. We’ll create this file in the `models` folder under the `shared` module.

Run the following command in your terminal.

```jsx
ng generate interface shared/models/search interface
```

This should create a `search.interface.ts` file. Copy the following code and paste it in there.

```jsx
export interface Video {
  videoId: string;
  videoUrl: string;
  channelId: string;
  channelUrl: string;
  channelTitle: string;
  title: string;
  publishedAt: Date;
  description: string;
  thumbnail: string;
}
```

Interfaces are one of the many features provided by TypeScript. If you aren’t familiar with how interfaces work, head to the [TypeScript docs](https://www.typescriptlang.org/docs/handbook/interfaces.html).

## Setting up the stylesheet

We’ll be using Semantic-UI to provide styling to our application so let’s quickly add that.

Head over to the `src` folder of the project, check for the `index.html` file, and paste the following code within the head tag.

```jsx
  <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.5/dist/semantic.min.css">
```

Your `index.html` file should look something like this:

```jsx
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>YoutubeSearch</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- Added Semantic Ui stylesheet -->
  <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.5/dist/semantic.min.css">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>
```

## Setting up the stateless components

### 1. Develop the search input component

The next step is to set up the stateless components. We’ll create the `search-input` component first. As previously stated, this component will contain everything that has to do with handling user input.

All stateless components will be in the `components` folder. Make sure you’re in the `app` directory in your terminal before running the following command.

```jsx
ng generate component search/components/search-input
```

This creates a `search-input` component. The great thing about using Angular’s CLI to generate components is that it creates the necessary files and sets up all boilerplate code, which eases a lot of the stress involved in setting up.

Add the following HTML code to the `search-input.html` file. This is just basic HTML code and styling using semantic UI:

```jsx
<div class="ui four column grid">
  <div class="ten wide column centered">
    <div class="ui fluid action input">
      <input
        #input
        type="text"
        placeholder="Search for a video...">
    </div>
  </div>
</div>
```

Take note of the `#input` line added to the input element. This is called a template reference variable because it provides a reference to the input element and allows us to access the element right from the component.

Before we start working on the component file, there are a few things to handle on the input side:

- Set up an event listener on the input element to monitor whatever the user types
- Make sure the value typed has a length greater than three characters
- It’s counterintuitive to respond to every keystroke, so we need to give the user enough time to type in their value before handling it (e.g., wait 500ms after the user stops typing before retrieving the value)
- Ensure the current value typed is different from the last valu. Otherwise, there’s no use in handling it

This is where RxJS comes into play. It provides methods called operators that help us implement these functionalities/use cases seamlessly.

Next, add the following code in the `search-input.component.ts` file.

```jsx
// search-input.component.ts

import { Component, AfterViewInit, ViewChild, ElementRef, Output, EventEmitter } from '@angular/core';
import { fromEvent } from 'rxjs';
import { debounceTime, pluck, distinctUntilChanged, filter, map } from 'rxjs/operators';

@Component({
  selector: 'app-search-input',
  templateUrl: './search-input.component.html',
  styleUrls: ['./search-input.component.css']
})
export class SearchInputComponent implements AfterViewInit {

  @ViewChild('input') inputElement: ElementRef;
  @Output() search: EventEmitter<string> = new EventEmitter<string>();

  constructor() { }

  ngAfterViewInit() {
    fromEvent(this.inputElement.nativeElement, 'keyup')
      .pipe(
        debounceTime(500),
        pluck('target', 'value'),
        distinctUntilChanged(),
        filter((value: string) => value.length > 3),
        map((value) => value)
      )
      .subscribe(value => {
        this.search.emit(value);
      });
  }

}
```

Let’s take a look at a few lines from the file above.

- `ViewChild('input')` gives us access to the input element defined in the HTML file previously. `'input'` is a selector that refers to the `#input` template reference variable we previously added to the input element in the HTML file
- `ngAfterViewInit` is a [lifecyle hook](https://angular.io/guide/lifecycle-hooks) that is invoked after the view has been initialized. In here, we set up all code that deals with the input element. This ensures that the view has been initialized and we can access the input element, thereby avoiding any unnecessary errors later on

Now let’s look at the part of the code found in the `ngAfterViewInit` method.

- The `fromEvent` operator is used to set up event listeners on a specific element. In this case, we’re interested in listening to the `keyup` event on the input element
- The `debounceTime()` operator helps us control the rate of user input. We can decide to only get the value after the user has stopped typing for a specific amount of time — in this case, 500ms
- We use the `pluck('target','value')` to get the value property from the input object. This is equivalent to `input.target.value`
- `distinctUntilChanged()` ensures that the current value is different from the last value. Otherwise, it discards it.
- We use the `filter()` operator to check for and discard values that have fewer than three characters
- The `map` operator returns the value as an `Observable`. This allows us to subscribe to it, in which case the value can be sent over to the parent component (which we’ve yet to define) using the `Output` event emitter we defined.

That’s all for the `search-input` component. We saw a tiny glimpse of just how powerful RxJS can be in helping us implement certain functionalities.

### 2. Develop the search list component

Now it’s time to set up the `search-list` component. As a reminder, all this component does is receive a list of videos from the parent component and renders it in the view.

Because this is also a stateless component, we’ll create it in the same folder as the `search-input` component. From where we left off in the terminal, go ahead and run the following command.

```jsx
ng generate component search/components/search-list
```

Then head over to the `search-list.component.ts` file created and paste the following code in there.

```jsx
// search-list.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { Video } from '../../../shared/models/search.interface';

@Component({
  selector: 'app-search-list',
  templateUrl: './search-list.component.html',
  styleUrls: ['./search-list.component.css']
})
export class SearchListComponent implements OnInit {

  @Input() videos: Video[];

  constructor() { }

  ngOnInit() {
  }
}
```

The file above is fairly straightforward. All it does is receive and store an array of videos from the parent component.

Let’s take a look at the HTML code, switch to the `search-input.html` file, and paste in the following code.

```jsx
<div class="ui four column grid">
  <div class="column" *ngFor="let video of videos">
    <div class="ui card">
      <div class="image">
        <img [src]="video.thumbnail">
      </div>
      <div class="content">
        <a class="header" style="margin: 1em 0 1em 0;">{{ video.title }}</a>
        <div class="meta">
          <span class="date" style="font-weight: bolder;">
            <a [href]="video.channelUrl" target="_blank">{{ video.channelTitle }}</a>
          </span>
          <span class="ui right floated date" style="font-weight: bolder;">{{ video.publishedAt | date:'mediumDate' }}</span>
        </div>
        <div class="description">
          {{ video.description?.slice(0,50) }}...
        </div>
      </div>
      <a [href]="video.videoUrl" target="_blank" class="extra content">
        <button class="ui right floated tiny red right labeled icon button">
          <i class="external alternate icon"></i>
          Watch
        </button>
      </a>
    </div>
  </div>
</div>
```

In the file above, we simply loop through the array of videos in our component and render them individually, this is done using the `*ngFor` directive found in the line above:

```jsx
<div class="column" *ngFor="let video of videos">
```

## Building the stateful component

Let’s create the parent component, `search-container`. This component will directly communicate with the search service sending over the user input and then pass the response to the `search-list` component to render.

Since the `search-container` is a stateful component, we’ll create this in a different directory than the other two components.

In the terminal once again, you should still be in the `app` directory. Type in the following command.

```jsx
ng generate component search/container/search-container
```

Before we start writing code, let’s take a step back and outline what we want to achieve. This component should be able to get user inputs from the `search-input` component. It should pass this over to the search service, which does the necessary operations and returns the expected result. The result should be sent over to the `search-list` component, where it will be rendered.

To implement these things, paste the following code into the `search-container.component.ts` file.

```jsx
// search-container.component.ts

import { Component } from '@angular/core';
import { SearchService } from 'src/app/shared/services/search.service';
import { Video } from 'src/app/shared/models/search.interface';

@Component({
  selector: 'app-search-container',
  templateUrl: './search-container.component.html',
  styleUrls: ['./search-container.component.css']
})
export class SearchContainerComponent {

  inputTouched = false;
  loading = false;
  videos: Video[] = [];

  constructor(private searchService: SearchService) { }

  handleSearch(inputValue: string) {
    this.loading = true;
    this.searchService.getVideos(inputValue)
      .subscribe((items: any) => {
        this.videos = items.map(item => {
          return {
            title: item.snippet.title,
            videoId: item.id.videoId,
            videoUrl: `https://www.youtube.com/watch?v=${item.id.videoId}`,
            channelId: item.snippet.channelId,
            channelUrl: `https://www.youtube.com/channel/${item.snippet.channelId}`,
            channelTitle: item.snippet.channelTitle,
            description: item.snippet.description,
            publishedAt: new Date(item.snippet.publishedAt),
            thumbnail: item.snippet.thumbnails.high.url
          };
        });
        this.inputTouched = true;
        this.loading = false;
      });
  }
}
```

In the code above, the `handleSearch` method takes in the user input as an argument. It then communicates with the `getVideos` method in the search service passing in the input value as an argument.

The `subscribe` function invokes this service call and the response from the `getVideos` method is passed to it as the `items` argument. We can then filter out the necessary values needed and add that to the videos array in the component.

Let’s quickly work on the HTML, paste this into `search-container.html` and we’ll go through it after:

```jsx
<div>
  <app-search-input (search)="handleSearch($event)"></app-search-input>
  <div *ngIf="inputTouched && !videos.length" class="ui four wide column centered grid" style="margin: 3rem;">
    <div class="ui raised aligned segment red warning message">
      <i class="warning icon"></i>
      <span class="ui centered" style="margin: 0 auto;">No Video Found</span>
    </div>
  </div>
  <div *ngIf="loading" style="margin: 3rem;">
    <div class="ui active centered inline loader"></div>
  </div>
  <app-search-list *ngIf="!loading" [videos]="videos"></app-search-list>
</div>
```

In the file above, we simply render both child components, `search-input` and `search-list`, and add the necessary input binding to the `search-list` component. This is used to send the list of videos retrieved from the service to the component. We also listen to an event from the `search-input` component that triggers the `handleSearch` function defined earlier.

Edge cases are also handled, such as indicating when no videos are found, which we only want to do after the input element has been touched by the user. The `loading` variable is also used to signify to the user when there’s an API call going on.

By default in every Angular application, there’s a root component, usually called the `app-root` component. This is the component that gets bootstrapped into the browser. As a result, we want to add the `search-container` component to be rendered there. The `search-container` component renders all other components.

Open the `app.component.html` file and paste the code below.

```jsx
<div class="ui centered grid" style="margin-top: 3rem;">
  <div class="fourteen wide column">
    <h1 class="ui centered aligned header">
      <span style="vertical-align: middle;">Youtube Search </span>
      <img src="/assets/yt.png" alt="">
    </h1>
    <app-search-container></app-search-container>
  </div>
</div>
```

## Testing out the Application

We’re all done! Now let’s go ahead and test our app.

In your terminal, run the following command to kickstart the application.

```jsx
ng serve
```

You may encounter an error similar to `ERROR in ../../node_modules/rxjs/internal/types.d.ts(81,44): error TS1005: ';' expected.` This doesn’t have to do with the code but rather the RxJS package installation. Luckily, there’s a [very straightforward and easy solution](https://github.com/ReactiveX/rxjs/issues/4540#issuecomment-469290223) to that.

By default, all Angular applications are served at `localhost:4200`, so go ahead and open that up in your browser. Here’s what it should look like:

![Completed YouTube Search App](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/07/final-youtube-search-app.gif?resize=600%2C340&ssl=1)

## Conclusion

You should now have a good understanding of how to use Angular and RxJS to build a YouTube video search application. We walked through how to implement certain core concepts by using them to build a simple application. We also got a sense of RxJS’s powerful features and discussed how it enables us to build certain functionalities with tremendous ease.

Best of all, you got a slick-looking YouTube search app for your troubles. Now you can take the knowledge you gained and implement even more complex features with the YouTube API.

* * * 
## Experience your Angular apps exactly how a user does 

Debugging Angular applications can be difficult, especially when users experience issues that are difficult to reproduce. If you're interested in monitoring and tracking Angular state and actions for all of your users in production, [try LogRocket.](logrocket.com/signup)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/qrpofyuvt10zrrkeqodr.png)

[LogRocket](logrocket.com/devto) is like a DVR for web apps, recording literally everything that happens on your site including network requests, JavaScript errors, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. 

The LogRocket NgRx plugin logs Angular state and actions to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred. 

Modernize how you debug your Angular apps – [Start monitoring for free.](logrocket.com/signup)
* * * 

The post [Build a YouTube video search app with Angular and RxJS](https://blog.logrocket.com/build-a-youtube-video-search-app-with-angular-and-rxjs/) appeared first on [LogRocket Blog](https://blog.logrocket.com).