MovieCard, MovieList, and the Browse Route
With the data layer in place, build the shared MovieCard component and the first two pages: Home and Browse.
The app shell
Section titled “The app shell”First, wire the root component. Update app.ts, app.html, and app.routes.ts:
import { Component } from '@angular/core';import { RouterOutlet } from '@angular/router';
@Component({ selector: 'app-root', standalone: true, imports: [RouterOutlet], template: `<router-outlet />`})export class App {}import { Routes } from '@angular/router';
export const routes: Routes = [ { path: '', loadComponent: () => import('./pages/home/home').then(m => m.Home) }, { path: 'browse', loadComponent: () => import('./pages/browse/browse').then(m => m.Browse) }, { path: 'search', loadComponent: () => import('./pages/search-results/search-results').then(m => m.SearchResults) }, { path: 'movies/:id', loadComponent: () => import('./pages/movie-detail/movie-detail').then(m => m.MovieDetail) }, { path: 'watchlist', loadComponent: () => import('./pages/watchlist/watchlist').then(m => m.Watchlist) }, { path: '**', redirectTo: '' }];Using loadComponent lazy-loads each page — they are only downloaded when the user navigates to them.
MovieCard component
Section titled “MovieCard component”src/app/components/movie-card/movie-card.ts:
import { Component, Input, inject } from '@angular/core';import { RouterLink } from '@angular/router';import { Movie } from '../../models/movie.model';import { WatchlistService } from '../../services/watchlist';import { posterUrl } from '../../core/tmdb.config';
@Component({ selector: 'app-movie-card', standalone: true, imports: [RouterLink], templateUrl: './movie-card.html', styleUrl: './movie-card.css'})export class MovieCard { @Input({ required: true }) movie!: Movie; private watchlist = inject(WatchlistService);
get isInWatchlist(): boolean { return this.watchlist.has(this.movie.id); }
get poster(): string { return posterUrl(this.movie.poster_path); }
toggle(event: Event): void { event.preventDefault(); this.watchlist.toggle(this.movie); }}movie-card.html:
<a [routerLink]="['/movies', movie.id]" class="card"> <div class="poster-wrapper"> <img [src]="poster" [alt]="movie.title" class="poster" loading="lazy" /> <button class="watchlist-btn" [class.saved]="isInWatchlist" (click)="toggle($event)" [attr.aria-label]="isInWatchlist ? 'Remove from watchlist' : 'Add to watchlist'" > {{ isInWatchlist ? '✓' : '+' }} </button> </div> <div class="card-info"> <h3 class="title">{{ movie.title }}</h3> <p class="year">{{ movie.release_date | slice:0:4 }}</p> <p class="rating">★ {{ movie.vote_average | number:'1.1-1' }}</p> </div></a>Home page
Section titled “Home page”src/app/pages/home/home.ts:
import { Component, OnInit, inject } from '@angular/core';import { MovieService } from '../../services/movie';import { MovieCard } from '../../components/movie-card/movie-card';import { Movie } from '../../models/movie.model';
@Component({ selector: 'app-home', standalone: true, imports: [MovieCard], templateUrl: './home.html', styleUrl: './home.css'})export class Home implements OnInit { private movieService = inject(MovieService);
trending: Movie[] = []; popular: Movie[] = []; loading = true;
ngOnInit(): void { this.movieService.getTrending().subscribe(movies => { this.trending = movies; }); this.movieService.getPopular().subscribe(movies => { this.popular = movies; this.loading = false; }); }}home.html:
<div class="home"> @if (loading) { <p class="loading">Loading movies…</p> } @else { <section> <h2>Trending This Week</h2> <div class="grid"> @for (movie of trending; track movie.id) { <app-movie-card [movie]="movie" /> } </div> </section> <section> <h2>Popular Now</h2> <div class="grid"> @for (movie of popular; track movie.id) { <app-movie-card [movie]="movie" /> } </div> </section> }</div>Browse page
Section titled “Browse page”The Browse page adds filter controls above the movie grid. See the complete implementation in the CinemaVault source — it uses FormBuilder, debounceTime, distinctUntilChanged, and switchMap exactly as you learned in Module 06.
Exercise
Section titled “Exercise”- Create
WatchlistServiceinsrc/app/services/watchlist.tswithsignal<Movie[]>,has(),toggle(), and localStorage persistence. (The full implementation is in the CinemaVault source.) - Implement
MovieCardas shown above. - Implement the
Homepage. - Navigate to
localhost:4200and confirm you see two movie grids. - Click the
+button on any card and confirm it toggles to✓and back.
- Use
loadComponentin routes for automatic lazy loading — each page is only bundled when visited. MovieCardaccepts a requiredmovieinput, reads watchlist state fromWatchlistService, and toggles via a button.HomecallsgetTrending()andgetPopular()inngOnInitand shows a loading state while requests are pending.BrowseusesFormBuilder+valueChanges+debounceTime+switchMapto filter movies reactively.