Search code examples
javascriptangulartypescript

What would be a viable and more flexible alternative to using snapshot in Angular 16?


I have been working on an SPA with Angular 16, TypeScript and The Movie Database (TMDB).

I run into a strange issue while working on a list movies by genre feature.

In app\services\movie-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 { GenreResponse } from '../models/Genre';

@Injectable({
  providedIn: 'root'
})

export class MovieService {
  constructor(private http: HttpClient) { }

  public getAllMovieGenres(): Observable<GenreResponse> {
    return this.http.get<GenreResponse>(`${environment.apiUrl}/genre/movie/list?api_key=${environment.apiKey}`);
  }

  public getMoviesByGenre(id: Number): Observable<MovieResponse> {
    return this.http.get<MovieResponse>(`${environment.apiUrl}/discover/movie?api_key=${environment.apiKey}&with_genres=${id}`);
  }
}

I use the above methods in the MoviesByGenre component:

import { Component } from '@angular/core';
import { GenreResponse, Genre } from '../../models/Genre';
import { MovieResponse, Movie } from '../../models/Movie';
import { MovieService } from '../../services/movie-service.service';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-movies-by-genre',
  templateUrl: './movies-by-genre.component.html',
  styleUrls: ['./movies-by-genre.component.scss']
})

export class MoviesByGenre {

  constructor(
    private activatedRoute: ActivatedRoute,
    private movieService: MovieService
  ) { }

  public genreName: string | undefined = '';

  public movieResponse!: MovieResponse;
  public movies: Movie[] | undefined = [];

  public genreResponse!: GenreResponse;
  public genres: Genre[] | undefined = [];

  public getMoviesByGenre(): void {

    // Get genre id (from URL parameter)
    const genre_id = Number(this.activatedRoute.snapshot.paramMap.get('id'));

    // Get genre name from genres array
    this.movieService.getAllMovieGenres().subscribe((response) => {
      this.genreResponse = response;
      this.genres = this.genreResponse.genres;

      if (this.genres && this.genres.length) {
        let currentGenre = this.genres.find(genre => genre.id === genre_id);
        if (currentGenre) {
          this.genreName = currentGenre.name || '';
          this.movieService.defaultTitle = this.genreName;
        }
      }
    });

    // Get movies by genre id
    this.movieService.getMoviesByGenre(genre_id).subscribe((response) => {
      this.movieResponse = response;
      this.movies = this.movieResponse.results;
    })
  }

  ngOnInit() {
    this.getMoviesByGenre();
  }
}

The problem

Whenever I display movies of a certain genre and try to navigate to another genre, for instance, from localhost:4200/by-genre/12 to localhost:4200/by-genre/18, the nre data is not loaded (even though the URL does change).

enter image description here

In other words using this to get and use the genre_id fails:

const genre_id = Number(this.activatedRoute.snapshot.paramMap.get('id'));

Questions

  1. What am I doing wrong?
  2. What is the easiest and most reliable way to fix this issue?

Solution

  • When you switch between variations of the same route ( the query params alone change ) the component does not reload, so to handle this scenario, angular router provides an observable which will notify when the route params changes, this can be used to trigger a rerun of genre fetch API.

    Its always a good idea to unsubscribe all subscriptions, during destroy of component

      public getMoviesByGenre(genre_id: any): void {
    
        // Get genre id (from URL parameter)
    
        // Get genre name from genres array
        this.subscription.add(
        this.movieService.getAllMovieGenres().subscribe((response) => {
          this.genreResponse = response;
          this.genres = this.genreResponse.genres;
    
          if (this.genres && this.genres.length) {
            let currentGenre = this.genres.find(genre => genre.id === genre_id);
            if (currentGenre) {
              this.genreName = currentGenre.name || '';
              this.movieService.defaultTitle = this.genreName;
            }
          }
        })
        );
    
        // Get movies by genre id
        
        this.subscription.add(this.movieService.getMoviesByGenre(genre_id).subscribe((response) => {
          this.movieResponse = response;
          this.movies = this.movieResponse.results;
        }));
      }
    
      ngOnInit() {
        this.subscription.add(
            this.activatedRoute.params.subscribe((params: Params) => {
                const id = params?.id;
                this.getMoviesByGenre(id);
            })
        );
      } 
    
      ngOnDestroy() {
        this.subscription.unsubscribe();
      }