Angular 18 Tutorial: Build a Movies App with Angular 18, HttpClient and Tailwind

Angular 18 Tutorial: Build a Movies App with Angular 18, HttpClient and Tailwind

In this tutorial, we'll be using Angular 18, the latest version of Angular, the popular platform for building front-end web applications with TypeScript. The latest version, released on May 22, brings a set of new features and enhancements that we will leverage to build a robust movies application.

We'll be building a movie application with Angular 18 and API, showcasing different concepts such as working with standalone components, services, consuming a REST API with HttpClient, Observables and infinite scrolling and playing videos. We'll also cover the use of the async pipe and various other pipes like the date and currency pipes. By the end of this angular 18 tutorial, you will have a solid understanding of how to build dynamic and interactive web applications using Angular 18. We'll also be using Tailwind CSS for styling our UI.

Our Angular 18 application will have the following features:

  1. Three pages: Home, Movies and Show Movie pages.
  2. Navbar and footer standalone components.
  3. Three sections for movies: Popular, Top Rated, and Now Playing.
  4. Image slider: A movies slider.
  5. Scrolling functionality: Buttons to navigate through movie lists.
  6. Movie cards: Each movie will be displayed in a card format containing the title, release date, and rating.
  7. API Integration: Get movie data from an external API.
  8. Responsive UI: A clean and responsive user interface using Tailwind CSS.
  9. Infinite scrolling.

Let's start with the home page. Here is the top of the home page we'll be building in our angular 18 tutorial: angular 18 tutorial

The top of the home page contains a movies slider that slides a set of movies displaying their images, titles, descriptions and release data.

Here is the bottom of the home page:

angular 18 tutorial

It contains three sections: Popular, Top and Now Playing sections. When we click on the buttons at the left and right sides we scroll horizontally through the movies. Each movie is displayed in a card format containing the title, release date and rating.

The second page is the movies pages with infinite scrolling where we scroll vertically through movies. Each movie is displayed in a card format containing the title, release date and rating:

angular 18 tutorial When the user scrolls down to the bottom, more movies will be fetched by Angular HttpClient and displayed.

The last page is the Show Movie page that looks like this on top:

angular 18 tutorial

It contains images of the movie plus the title and overview and the actors. It also contains the Play Now button the plays a trailer video of the movie.

At the bottom, it looks like this:

angular 18 tutorial

It contains an horizontally scrolling standalone component of the similar movies.

All pages contains a navbar and footer. The navigation bar contains links to the home and movies pages.

Let's start our angular 18 tutorial.

Angular 18 Tutorial: Installing Angular CLI v18 & Creating a Project

To start building our movies application with Angular 18, we need to first install the Angular CLI (Command Line Interface) version 18. The Angular CLI is a powerful tool that helps in automating various tasks when developing Angular applications, such as creating new projects, generating components, and running tests.

Step 1: Install Node.js and npm

Ensure you have Node.js and npm (Node Package Manager) installed on your system. You can download and install them from the official Node.js website. Once installed, you can verify the installation by running the following commands in your terminal:

node -v
npm -v

These commands should display the versions of Node.js and npm installed on your system.

Step 2: Install Angular CLI

To install Angular CLI v18, open your terminal and run the following command:

npm i --global @angular/cli

The --global flag installs the Angular CLI globally on your system, allowing you to use the ng command from anywhere.

Step 3: Verify the Installation

After the installation is complete, verify that Angular CLI v18 is installed by running:

ng version

This command will display the installed Angular CLI version along with other related package versions.

Creating a New Angular 18 Project

With Angular CLI installed, we can now create a new Angular 18 project for our movies application.

Step 1: Create a New Project

Run the following command to create a new Angular project named movies-app:

ng new movies-app

The CLI will prompt you to select various configuration options, such as which stylesheets format to use. For this angular 18 tutorial, choose the following options:

? Which stylesheet format would you like to use? CSS
? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? No

Step 2: Navigate to the Project Directory

After the project is created, navigate to the project directory:

cd movies-app

Angular 18 Tutorial: Configuring Tailwind

Tailwind CSS is a utility-first CSS framework that allows you to build custom designs without leaving your HTML. In this section, we will configure Tailwind CSS in our Angular application to leverage its powerful styling capabilities.

First, we need to install Tailwind CSS along with PostCSS and Autoprefixer. Open your terminal and navigate to the root directory of your Angular project. Then, run the following commands:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init

The npx tailwindcss init command creates a tailwind.config.js file, which is used to configure Tailwind CSS.

Open tailwind.config.js and add the paths to all of your template files. Inside the content array, add:


This configuration tells Tailwind CSS to remove any unused styles in production by scanning all .html and .ts files in the src directory.

We need to import Tailwind CSS into our global styles file. Open the src/styles.css file and add the following lines:

@tailwind base;
@tailwind components;
@tailwind utilities;

These directives include Tailwind's base styles, component classes, and utility classes into your project.

To ensure that Tailwind CSS is working correctly, let's add some Tailwind classes to our app.component.html file. Open src/app/app.component.html and replace its content with the following:

<div class="flex w-full h-screen items-center justify-center bg-slate-600">
  <h1 class="text-3xl font-bold">Hello Tailwind!</h1>

This code snippet uses Tailwind utility classes to style the page, setting a minimum height for the screen, centering content both vertically and horizontally, applying a background color, and styling the text.

Now, start the development server to see the changes:

ng serve

Open your browser and navigate to http://localhost:4200/. You should see a styled page with the message "Hello Tailwind!":

angular 18 tutorial

At this point, we have successfully installed Angular CLI v18, created a new Angular project named movies-app and installed and configured Tailwind CSS in our Angular 18 application. We've also verified that Tailwind CSS is working by adding some utility classes to our app.component.html file. Tailwind CSS is now ready to be used for styling the rest of our movies application.

Next, we will proceed with building the core components and integrating the movies API to fetch and display movie data. Here's an overview of what we'll cover:

  1. Creating Components: We'll create reusable Angular standalone components for the navbar, footer, and movie cards.
  2. Setting Up Services: We'll set up an Angular service to handle API calls for fetching movie data.
  3. Building the Home Page: We'll build the home page, which includes a movie slider and sections for Popular, Top Rated, and Now Playing movies.
  4. Implementing the Movies Page: We'll implement the movies page with infinite scrolling to load more movies as the user scrolls down.
  5. Creating the Show Movie Page: We'll create a detailed view for individual movies.

In the next steps, we will start building the standalone components and services for our movies application, integrating it with an external API to fetch movie data, and adding the necessary functionality to display and interact with the movie data.

Adding & styling the navigation bar component with Tailwind CSS

To enhance the navigation experience in our movies application, we'll create a reusable standalone Navbar component. This component will be added to our main application component to ensure it appears on every page.

Using Angular CLI, generate a new standalone component called navbar:

ng g c components/navbar

This command creates the following files in the src/app/components/navbar directory:

  • navbar.component.ts
  • navbar.component.html
  • navbar.component.css
  • navbar.component.spec.ts

Next, we need to add the navbar component to the app standalone component. First, in the src/app/app.component.ts file, import NavbarComponent and add it the imports array:

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { NavbarComponent } from './components/navbar/navbar.component';

  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, NavbarComponent],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
export class AppComponent {
  title = 'moviesapp';

Next, in the src/app/app.component.html include the component:

<div class="bg-black">

Open the src/app/components/navbar/navbar.component.ts file and import the router module:

import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';

  selector: 'app-navbar',
  standalone: true,
  imports: [RouterModule],
  templateUrl: './navbar.component.html',
  styleUrl: './navbar.component.css'
export class NavbarComponent {}

Next, we'll define the layout for our navigation bar. Open the src/app/components/navbar/navbar.component.html file and add the following HTML:

<nav class="fixed top-0 w-full h-12 flex bg-slate-700 text-white justify-between z-50">
  <div class="flex items-center gap-10 px-10">
    <img class="w-9" src="logo.svg" />
    <a routerLink="/">Home</a>
    <a routerLink="/movies">Movies</a>

This <nav> element creates a fixed navigation bar at the top of the viewport. The bar has a dark slate background and white text. It uses Flexbox for layout, ensuring the logo and navigation links are evenly spaced and centered vertically within the bar. The navigation bar remains on top of other content due to the z-50 z-index setting.

By structuring the <nav> element this way, we create a responsive and visually appealing navigation bar that enhances the user experience on our Angular application.

This is how it looks:

angular 18 tutorial

Just like we did with the navbar component, generate the footer component and include it in the app component below the router outlet:

<div class="bg-black">

In the src/app/components/footer/footer.component.html add the following HTML code:

  <p class="bg-slate-700 text-white text-center mt-20">
    Created by Techiediaries

Creating Angular 18 Routing Components

To build our movies application, we'll need different pages to display various sections like Home, Movies, and a detailed Show Movie page. We'll achieve this by creating routing components and setting up routes in Angular.

In the terminal, run the following commands:

ng g c pages/home
ng g c pages/movies
ng g c pages/show-movie

Open the src/app/app.routes.ts file and add the routes to different components:

import { Routes } from '@angular/router';
import { HomeComponent } from './pages/home/home.component';
import { MoviesComponent } from './pages/movies/movies.component';
import { ShowMovieComponent } from './pages/show-movie/show-movie.component';

export const routes: Routes = [
    path: '',
    component: HomeComponent,
    pathMatch: 'full',
    path: 'movies',
    component: MoviesComponent,
    path: 'show-movie/:movieId',
    component: ShowMovieComponent,

The routes array contains route configurations that define how different paths in our Angular application should be handled. Each route configuration specifies a path and the component that should be displayed when that path is accessed. Dynamic route parameters can also be used to create more flexible routes that handle varying data. With this route configuration, our application knows how to navigate to different views based on the URL.

The first route configuration specifies that when the root path ('') is accessed, the HomeComponent should be displayed. The pathMatch: 'full' ensures that the route matches only when the entire URL matches the empty string.

The second route configuration specifies that when the /movies path is accessed, the MoviesComponent should be displayed.

The last route configuration specifies a dynamic route parameter :movieId within the path /show-movie/:movieId. When a URL matching this pattern is accessed (e.g., /show-movie/123), the ShowMovieComponent is displayed. The movieId parameter can be accessed within the component to fetch details for the specific movie.

Ensuring Type Safety of our Angular 18 App

To ensure type safety and clarity in our Angular application, we'll define interfaces for the data structures we expect from our movie API. Interfaces help us define the shape of data objects and make our code more readable and maintainable. We'll create interfaces for movies, movie lists, credits, actors, images, and videos.

Create a src/app/models/movie.ts file and add the following interface for a movie:

export interface Movie {
  id: number;
  adult: boolean;
  backdrop_path: string;
  genre_ids: number[];
  original_language: string;
  original_title: string;
  overview: string;
  popularity: number;
  poster_path: string;
  release_date: string;
  title: string;
  video: boolean;
  vote_average: number;
  vote_count: number;
  revenue?: number;
  runtime?: string;
  status?: string;
  genres?: any[];

This interface outlines the structure of a movie object, including optional properties like revenue, runtime, status, and genres.

Next, add the movies interface:

export interface Movies {
  page: number;
  results: Movie[];
  total_pages: number;
  total_results: number;

The Movies interface includes pagination information and an array of Movie objects.

Next, create a src/app/models/credit.ts file and add interface for movie credits:

export interface Credits {
  cast: Actor[];

This interface represents the credits of a movie, containing an array of Actor objects.

Next, add an interface for movie actors:

export interface Actor {
  name: string;
  profile_path: string;
  character: string;
  id: number;

The Actor interface describes an actor's basic details, including their name, profile picture path, character they played, and ID.

Next, create a src/app/models/image.ts file and add the following interfaces:

export interface Images {
  backdrops: Image[];

export interface Image {
  file_path: string;

The Images interface includes an array of Image objects, and the Image interface represents an individual image with a file path. Next, create a src/app/models/video.ts file and add:

export interface Videos {
  results: Video[];
  id: string;

export interface Video {
  key: string;
  site: string;

The Videos interface includes an array of Video objects, and the Video interface describes a video's key and the site where it's hosted.

Adding a Movies API Angular 18 Service

To fetch movie data from an external API and manage API interactions efficiently, we'll create a service in Angular. This service will use HttpClient to make HTTP requests to a movie database API.

Step 1: Generate the Movies Service

First, head to a terminal and run the following command to generate a new service:

ng g s services/movies

This command creates a new service file (movies.service.ts) in the src/app/services directory.

Step 2: Generate Environment Variables

Next, generate the environment variables files using the Angular CLI:

ng g environments

This command creates environment configuration files in the src/environments directory.

Step 3: Add API Key to Environment Variables

Open the src/environments/environment.development.ts file and add your API key for accessing the movie database:

export const environment = {
    apiKEY: 'dadb019730c0075868955d1ec94040bb'

Make sure to replace dadb019730c0075868955d1ec94040bb with your own API key.

Step 4: Provide HttpClient in App Configuration

To use HttpClient in your Angular 18 application, you need to provide it in your application's configuration. Open the src/app/app.config.ts file and add the following code:

// [...]
import { provideHttpClient } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [..., provideHttpClient()]

Step 5: Implement the Movies Service

Now, let's implement the methods to fetch movie data in the MoviesService. Open the src/app/services/movies.service.ts file and start by adding the following imports:

import { Injectable, inject } from '@angular/core';
import { environment } from '../../environments/environment';
import { HttpClient } from '@angular/common/http';
import { Movie, Movies } from '../models/movie';
import { map } from 'rxjs';
import { Videos } from '../models/video';
import { Credits } from '../models/credit';
  • inject for injecting services.
  • environment: Imports environment variables where API key is stored.
  • HttpClient: Used to make HTTP requests to the API.
  • Movie, Movies, Videos, Credits: Interfaces defining the structure of the data returned by the API.

Defining Base URL for Movie Images

Next, define and export the base URL for fetching movie images from the API.

export const imagesBaseUrl = '';

Initializing Variables and Injecting HttpClient

Next, define the following variables and inject HttpClient:

export class MoviesService {
  private apiUrl = '';
  private apiKey = environment.apiKEY;
  private httpClient = inject(HttpClient);
  constructor() { }

This initializes private properties for API URL, API key, and an instance of HttpClient.

Getting Movies by Type

Next, define the fetchMoviesByType method:

  fetchMoviesByType(type: string, pageNumber = 1) {
    return this.httpClient

This method will be used to fetch movies of a specific type (e.g., popular, top-rated) from the API. It accepts type (e.g., 'popular', 'top_rated') and pageNumber as parameters and constructs the API URL with the specified type and page number and makes an HTTP GET request using HttpClient.

Getting Similar Movies

Next, define the fetchSimilarMovies method:

  fetchSimilarMovies(id: string) {
    return this.httpClient
      .pipe(map((data)=> data.results));

This method will be used to fetch movies similar to a specified movie by its ID. It constructs the API URL for similar movies and makes an HTTP GET request. It uses pipe and map operators from RxJS to extract the results property from the API response.

Getting Movie by ID

Next, define the fetchMovieById method:

  fetchMovieById(id: string) {
    return this.httpClient.get<Movie>(

This method will be used to fetch details of a specific movie by its ID. It constructs the API URL for the movie details and makes an HTTP GET request.

Getting Movie Videos

Next, define the fetchMovieVideos method:

  fetchMovieVideos(id: string) {
    return this.httpClient
      .pipe(map((data) => data.results))

This method will be used to fetch videos (trailers, teasers) associated with a specific movie by its ID. It constructs the API URL for movie videos and makes an HTTP GET request. It uses pipe and map operators to extract the results property from the API response.

Getting Movie Cast

Finally, define the fetchMovieCast method:

  fetchMovieCast(id: string) {
    return this.httpClient
      .pipe(map((data) => data.cast))

This method will be used to fetch the cast (actors) of a specific movie by its ID. It constructs the API URL for movie credits and makes an HTTP GET request. It uses pipe and map operators to extract the cast property from the API response.

This MoviesService provides methods to fetch various movie-related data from an external API. It encapsulates the logic for making HTTP requests and handling API responses, allowing other parts of the application to easily consume and utilize movie data.


We've successfully set up a Movies API service in Angular 18 by following these steps:

  1. Generated the Movies Service: Created a service using Angular CLI v18.
  2. Configured Environment Variables: Added the API key in the environment configuration file.
  3. Provided HttpClient: Configured HttpClient in the application's configuration.
  4. Implemented API Methods: Added methods in the MoviesService to fetch popular, top-rated, and now-playing movies, as well as movie details, credits, images, and videos.

With this setup, our Angular 18 application can now interact with the movie database API to fetch and display movie data.

Implementing the Movie Component

The MovieComponent is an Angular standalone component responsible for rendering the visual representation of a single movie. It receives movie data through its movie input property and utilizes Angular's template and styling capabilities to present the movie information to the user.

Open a terminal and run the following command:

ng g c components/movie

Open the src/app/components/movie/movie.component.ts file and add the following imports:

import { Input } from '@angular/core';
import { Movie } from '../../models/movie';
import { DatePipe } from '@angular/common';
import { imagesBaseUrl } from '../../services/movies.service';
import { RouterModule } from '@angular/router';

Next, add the date pipe, and router module to the imports array of the standalone component:

  selector: 'app-movie',
  standalone: true,
  imports: [DatePipe, RouterModule],
  templateUrl: './movie.component.html',
  styleUrl: './movie.component.css'

Next, define a public property imagesBaseUrl and an input property named movie of type Movie:

export class MovieComponent {
  public imagesBaseUrl = imagesBaseUrl;
  @Input() movie!: Movie;

The @Input() decorator indicates that the property can receive data from its parent component. The ! symbol after movie is TypeScript's non-null assertion operator, which tells TypeScript that movie will be initialized by the parent component and won't be null or undefined.

For the component template, add the following HTML:

  routerLink="/show-movie/{{ }}"
  class="w-full min-w-[230px] h-90 overflow-hidden block rounded relative hover:scale-105 transition-all">

    [src]="imagesBaseUrl + '/w185/' + movie.poster_path"

  <div class="absolute bottom-0 h-30 bg-black/60 p-2 w-full text-white">
    <h2 class="text-ellipsis text-lg font-semibold">
      {{ movie.title }}

    <div class="flex justify-between bottom-0">
        {{ movie.release_date | date }}
      <p class="bg-black text-white rounded m-0 text-sm">
        Rating: {{ movie.vote_average }}

This component allows users to click on a movie poster to view its details. It dynamically populates movie data and handles conditional rendering of the poster image. Overall, it provides a visually appealing and interactive way to browse and explore movies.

This is how it should look like:

angular 18 tutorial

Implementing the Movies Component with Infinite Scrolling

The movies page with infinite scrolling provides users with a convenient and engaging way to explore a large collection of movies, presenting essential details in an aesthetically pleasing card format while offering seamless navigation and responsive design.

The page dynamically loads additional movie cards as the user scrolls down, providing a seamless browsing experience without the need for pagination.

Go back to your terminal and run the following command:

npm install ngx-infinite-scroll --save

Next, open the src/app/pages/movies/movies.component.ts file and start by adding the following imports:

import { Component, DestroyRef, inject } from '@angular/core';
import { MoviesService } from '../../services/movies.service';
import { AsyncPipe } from '@angular/common';
import { Movie } from '../../models/movie';
import { MovieComponent } from '../../components/movie/movie.component';
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

The import statements in the Angular component file provide essential dependencies for building a functional component. They include the @Component decorator for marking components, the movies service for data fetching, the async pipe for handling asynchronous data, the Movie interface type safety, the movie component for visual representation, and a module for additional features like infinite scrolling. These imports collectively enable the component to interact with data, implement UI features, and manage resources effectively.

Next, add the async pipe, movie component and infinite scroll module to the imports array of the standalone component:

  selector: 'app-movies',
  standalone: true,
  imports: [AsyncPipe, MovieComponent, InfiniteScrollModule],
  templateUrl: './movies.component.html',
  styleUrl: './movies.component.css'

Next, inject the movies and DestroyRef services and call the fetchMoviesByType method as follows:

private  moviesService  =  inject(MoviesService);
private  pageNumber  =  1;
private destroyRef  =  inject(DestroyRef)
public  moviesObs$  =  this.moviesService.fetchMoviesByType('popular', this.pageNumber);
public  moviesResults:  Movie[] = [];

These initialize properties within the component's class, including a service instance for fetching movies, a page number, and a resource for managing component destruction. It also defines an observable property for storing fetched movie data and an array for storing the results.

Next, in the ngOnInit() life-cycle method, subscribe to the movies observable and assign the results to the moviesResults array:

  this.moviesObs$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => {
    this.moviesResults  =  data.results;

In the ngOnInit lifecycle hook, we're subscribing to the moviesObs$ observable to get movie data and update the moviesResults property when data is received. We use the pipe operator to chain operators onto the observable. The takeUntilDestroyed operator is used here to automatically unsubscribe from the observable when the component is destroyed, preventing memory leaks.

Overall, this ensures that movie data is fetched when the component is initialized, and the moviesResults property is updated accordingly. The use of takeUntilDestroyed ensures that subscriptions are properly cleaned up when the component is destroyed, preventing memory leaks.

Next, define the onScroll() method which gets called when the user scrolls to the bottom:

onScroll():  void {
  this.moviesObs$  =  this.moviesService.fetchMoviesByType('popular', this.pageNumber);
  this.moviesObs$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => {
    this.moviesResults  =  this.moviesResults.concat(data.results);

This function helps with the continuous loading of movie data as the user scrolls down the page, providing a seamless browsing experience. It ensures that new movie data is fetched and added to the existing movies array without losing previously fetched data.

It increments the pageNumber property, indicating that the next page of data should be fetched. Then, fetches movies for the next page using the fetchMoviesByType method of the moviesService. It subscribes to the returned observable to handle the emitted movie data. It also uses takeUntilDestroyed to ensure the subscription is cleaned up properly when the component is destroyed. Within the subscription callback, the new movie results are concatenated with the existing moviesResults array. This ensures that the new movie data is appended to the existing list, preserving previously fetched data.

In the component template movies.component.html, add the following HTML:

<div infiniteScroll (scrolled)="onScroll()" class="container mx-auto pt-20">
  <div class="grid grid-cols-[repeat(auto-fit,230px)] gap-5 justify-center">
    @for(movie of moviesResults; track{
      <app-movie [movie]="movie"></app-movie>

This code adds a movies container with an infinite scroll feature and a grid layout to display movie cards. The container div element uses the infiniteScroll directive to enable infinite scrolling functionality. The (scrolled) event is bound to the onScroll() method, which is triggered when the user scrolls. Using the @for syntax, it loops through each movie in the moviesResults array, tracking the id of each movie for efficient rendering and renders the movie component while binds the movie object to the movie input property of the app-movie component, passing the movie data for rendering.

With these changes, our movies page should now have infinite scrolling functionality, allowing users to scroll through a large collection of movies seamlessly. New movies will be fetched and appended to the existing list as the user scrolls down the page.

You can implement the other components in the same way. If you are stuck, take a look the code from this repository.


Throughout this angular 18 tutorial, we've created a movies application with Angular 18, Tailwind CSS, HttpClient and an external REST API.

  • Date: