skip to Main Content

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

I have made a component that displays movies by genre:

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 MoviesByGenreComponent {
  public movieResponse!: MovieResponse;
  public movies: Movie[] | undefined = [];

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

  public genreName: string | undefined = '';

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


  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;
        }
      }
    });

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

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

In the service used by the above component, 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';
import { GenreResponse } from '../models/Genre';
import { TrailerResponse } from '../models/Trailer';

@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}`);
  }
}

In the routing module, I have:

const routes: Routes = [
  {
    path: '',
    component: HomePageComponent,
    data: { title: 'Now playing', animation: 'isRight' },
  },
  {
    path: 'by-genre/:id',
    component: MoviesByGenreComponent,
    data: { title: '', animation: 'isLeft' },
  },
  {
    path: 'movie/:id',
    component: MovieDetailsComponent,
    data: { title: '', animation: 'isLeft' },
  },
  {
    path: 'actor/:id',
    component: ActorDetailsComponent,
    data: { title: '', animation: 'isRight' },
  },
  {
    path: '**',
    component: NotFoundComponent,
    data: { title: '', animation: 'isRight' },
  },
];

From the routing module, unless the title property in the data object is empty, I display the page title in appapp.component.html:

<div class="container">
  <h1 *ngIf="title.length" class="page-title text-success mt-2 mb-3">{{ title }}</h1>
  <router-outlet></router-outlet>
</div>

For the MoviesByGenreComponent, I want to send the dynamically obtained genre name (the variable genreName), from the component to the router and assign genreName to the title property.

The desired result:

The desired result

Stackblitz

There is a stackblitz with all the code I have so far.

What is the most reliable way to achieve this result?

2

Answers


  1. You can store the post title content on the service and use a function getFullTitle to always fetch the latest value, the only caveat for this approach is you need to ensure the service title be set to an empty string everytime the component gets destroyed!

    movie by genre.com.ts

    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 MoviesByGenreComponent {
      public movieResponse!: MovieResponse;
      public movies: Movie[] | undefined = [];
    
      public genreResponse!: GenreResponse;
      public genres: Genre[] | undefined = [];
    
      public genreName: string | undefined = '';
    
      constructor(
        private activatedRoute: ActivatedRoute,
        private movieService: MovieService
      ) {}
    
      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.postTitle = ` - ${this.genreName}`;
            }
          }
        });
    
        // Get movies by genre id
        this.movieService.getMoviesByGenre(genre_id).subscribe((response) => {
          this.movieResponse = response;
          this.movies = this.movieResponse.results;
        });
      }
    
      ngOnInit() {
        this.movieService.postTitle = '';
        this.getMoviesByGenre();
      }
    }
    

    app.com.html

    <app-top-bar></app-top-bar>
    
    <div class="container" [@routeAnimations]="prepareRoute(outlet)">
      <h1
        *ngIf="getFullTitle() as finalTitle"
        class="page-title text-success mt-2 mb-3"
      >
        {{ finalTitle }}
      </h1>
      <router-outlet #outlet="outlet"></router-outlet>
    </div>
    
    <app-footer class="mt-auto"></app-footer>
    

    app.com.ts

    import { Component } from '@angular/core';
    import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
    import { RouterOutlet } from '@angular/router';
    import { slider } from './route-animations';
    import { MovieService } from './services/movie-service.service';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.scss'],
      animations: [slider],
    })
    export class AppComponent {
      public title: String = 'Movies';
    
      constructor(
        private route: ActivatedRoute,
        private router: Router,
        private movieService: MovieService
      ) {
        this.router.events.subscribe((event) => {
          if (event instanceof NavigationEnd && this.route.root.firstChild) {
            this.title = this.route.root.firstChild.snapshot.data['title'];
          }
        });
      }
    
      public getFullTitle() {
        return this.title + this.movieService.postTitle;
      }
    
      public prepareRoute(outlet: RouterOutlet) {
        return (
          outlet &&
          outlet.activatedRouteData &&
          outlet.activatedRouteData['animation']
        );
      }
    }
    

    Stackblitz Demo

    Login or Signup to reply.
  2. I have refactored your code in several places. The Stackblitz Demo has been updated accordingly. Most files contain comments on how you can further improve your code.

    I didn’t delete your previous code so that you can compare it with the refactored version.

    Check each file or search for new comments

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search