I have been working on an SPA with Angular 16, TypeScript and The Movie Database (TMDB).
I run into a strange problem while working on a movies search feature.
In appservicesmovie-service.service.ts
I have:
import { environment } from '../../environments/environment';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { MovieResponse, Movie } from '../models/Movie';
@Injectable({
providedIn: 'root'
})
export class MovieService {
constructor(private http: HttpClient) {}
public searchMovies(searchTerm: string): Observable<MovieResponse> {
return this.http.get<MovieResponse>(`${environment.apiUrl}/search/movie?api_key=${environment.apiKey}&query=${searchTerm}`);
}
}
I use the method above inside the TopBarComponent
like this:
import { Component } from '@angular/core';
import { MovieResponse, Movie } from '../../models/Movie';
import { MovieService } from '../../services/movie-service.service';
@Component({
selector: 'app-top-bar',
templateUrl: './top-bar.component.html',
styleUrls: ['./top-bar.component.scss']
})
export class TopBarComponent {
constructor(private movieService: MovieService) { }
public searchTerm: string = '';
public isSearch: boolean = false;
public timeOutInterval: number = 500;
public searchResultsResponse!: MovieResponse;
public searchResults: Movie[] | undefined = [];
public hideSearchResults(): void {
this.isSearch = false;
}
public debounceMovieSearch(): void {
setTimeout(() => this.doMovieSearch(), this.timeOutInterval);
}
public doMovieSearch() {
if (this.searchTerm && this.searchTerm.length > 2) {
this.isSearch = true;
this.movieService.searchMovies(this.searchTerm).subscribe((response) => {
this.searchResultsResponse = response;
this.searchResults = this.searchResultsResponse.results;
})
} else {
this.isSearch = false;
}
}
}
Search form:
<form class="search_form w-100 mx-auto mt-2 mt-md-0">
<div class="input-group">
<input type="search" name="search" [(ngModel)]="searchTerm" (input)="debounceMovieSearch()" placeholder="Search" autocomplete="off" class="form-control search-box">
<button class="btn btn-search" type="button">Search</button>
</div>
<div *ngIf="isSearch" (clickOutside)="hideSearchResults()" class="search-results shadow-sm">
<div *ngIf="searchResults && searchResults.length">
<a routerLink="/movie/{{ movie.id }}" *ngFor="let movie of searchResults">
<app-search-item [movie]="movie"></app-search-item>
</a>
</div>
<div *ngIf="!(searchResults && searchResults.length)">
<p class="m-0 p-2 text-center">No movies found for this search</p>
</div>
</div>
</form>
The routes in the appapp-routing.module.ts
:
const routes: Routes = [
{ path: '', component: HomePageComponent, data: { title: 'Now playing' } },
{ path: 'top-rated', component: TopMoviesComponent, data: { title: 'Top Rated' } },
{ path: 'movie/:id', component: MovieDetailsComponent, data: { title: '' } },
{ path: 'actor/:id', component: ActorDetailsComponent, data: { title: '' } },
{ path: '**', component: NotFoundComponent, data: { title: '' } },
];
The result looks like this:
The problem
Clicking a movie item in the searcg results list will navigate us to the movie details route (MovieDetailsComponent
), except when we already are on a movie details page.
Stackblitz
There is a Stackblitz with all the code I have so far.
Questions
- What am I doing wrong?
- What is the most reliable way to fix this issue?
2
Answers
You are almost correct, what you are missing is using
paramMap
rather thansnapshot
in themovie-details.component
, so you can react to the movieid
changes.You need to address this change on the function
getMovieDetails
like so:Additionally, since
paramMap
returns anobservable
, keep in mind that you should onlysubscribe
in one place, that is why I am usingpipe
and returning the final observable result, which is either aMovie
orundefined
When you navigate from
movie/1
tomovie/2
,MovieDetailsComponent
isn’t recreated. Because you don’t listen for id changes, the component doesn’t know that movie id has changed and it needs to update the view. So listen to route parameter changes: