Как изолировать вызов API для одного компонента в этом приложении Angular 16?

Я работал над SPA с Angular 16, TypeScript и The Movie Database (TMDB).

Я столкнулся с проблемой после разработки функции «бесконечная прокрутка».

В компоненте MoviesByGenre у меня есть:

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';
import { distinctUntilChanged, fromEvent, map, startWith } from 'rxjs';

@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[] = [];
  public genreResponse!: GenreResponse;
  public genres: Genre[] | undefined = [];
  public genreId!: number;
  public maxPage: number = 10;
  public pageNumber: number = 1;
  public isLoading: boolean = false;

  public getMoviesByGenre(): void {
    // Get genre id (from URL parameter)
    this.genreId = 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 === this.genreId
        );
        if (currentGenre) {
          this.genreName = currentGenre.name || '';
          this.movieService.defaultTitle = this.genreName;
        }
      }
    });

    this.loadMoreMovies(this.genreId, this.pageNumber);
  }

  public loadMoreMovies(genreId: number, pageNumber: number) {
    // Get movies by genre id
    this.movieService
      .getMoviesByGenre(genreId, pageNumber)
      .subscribe((response) => {
        this.movieResponse = response;
        this.movies.push(...(this.movieResponse?.results || []));
      });
  }

  ngAfterViewInit() {
    fromEvent(window, 'scroll')
      .pipe(
        startWith(0),
        map(() => window?.scrollY),
        distinctUntilChanged()
      )
      .subscribe((scrollPos: any) => {
        if (!this.movies?.length) {
          return;
        }
        if (
          Math.round(scrollPos + window.innerHeight) >=
          document.documentElement.scrollHeight &&
          this.pageNumber < this.maxPage
        ) {
          this.pageNumber++;
          this.loadMoreMovies(this.genreId, this.pageNumber);
        }
      });
  }

  ngOnInit() {
    this.activatedRoute.params.subscribe(() => {
      this.movies = [];
      this.getMoviesByGenre();
    });
  }

  ngOnDestroy() {
    this.movieService.defaultTitle = '';
  }
}

В сервисе MovieService:

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 defaultTitle: string = '';


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

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

  public getMovieDetails(id: Number): Observable<Movie>{
    return this.http.get<Movie>(`${environment.apiUrl}/movie/${id}?api_key=${environment.apiKey}`);
  }
}

При каждой прокрутке страницы вниз по маршруту by-genre/:id загружается еще 20 фильмов с ограничением в 10 страниц (200 фильмов).

Проблема

Проблема в том, что даже на других маршрутах, таких как маршрут с подробностями фильма, при прокрутке страницы вниз выполняется вызов API, который показывает счетчик загрузки, для которого я без необходимости использовал технику перехватчика http.

Цель

Цель состоит в том, чтобы сделать бесконечную прокрутку специфичной для компонента MoviesByGenre:

Для этой цели я добавил логическое значение в компонент MoviesByGenre и использовал его следующим образом:

public isLoadMore: boolean = true;

if (this.isLoadMore) {
  this.loadMoreMovies(this.genreId, this.pageNumber);
} 

Но запрос по-прежнему выполняется для других компонентов при прокрутке страницы вниз.

Похоже, мне не удалось ни определить причину проблемы, ни предложить жизнеспособное решение.

Вопросы

  1. Что вызывает эту проблему?
  2. Какой самый надежный способ исправить?
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
0
138
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы решить эту проблему, нам нужно unsubscribe перейти к активным подпискам. Когда компонент уничтожается, это не обязательно означает, что подписки уничтожаются. Нам нужно add их всех subscription и вызвать unsubscribe метод ngOnDestroy компонента, чтобы уничтожить все подписки.

Рекомендуется добавлять все подписки в свойство подписки, а затем полностью от них отказываться.

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';
import {
  Subscription,
  distinctUntilChanged,
  fromEvent,
  map,
  startWith,
} from 'rxjs';

@Component({
  selector: 'app-movies-by-genre',
  templateUrl: './movies-by-genre.component.html',
  styleUrls: ['./movies-by-genre.component.scss'],
})
export class MoviesByGenre {
  private subscription = new Subscription(); // <- changed here!
  constructor(
    private activatedRoute: ActivatedRoute,
    private movieService: MovieService
  ) {}

  public genreName: string | undefined = '';
  public movieResponse!: MovieResponse;
  public movies: Movie[] = [];
  public genreResponse!: GenreResponse;
  public genres: Genre[] | undefined = [];
  public genreId!: number;
  public maxPage: number = 10;
  public pageNumber: number = 1;
  public isLoading: boolean = false;

  public getMoviesByGenre(): void {
    // Get genre id (from URL parameter)
    this.genreId = Number(this.activatedRoute.snapshot.paramMap.get('id'));

    // Get genre name from genres array
    this.subscription.add( // <- changed here!
      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 === this.genreId
          );
          if (currentGenre) {
            this.genreName = currentGenre.name || '';
            this.movieService.defaultTitle = this.genreName;
          }
        }
      })
    );

    this.loadMoreMovies(this.genreId, this.pageNumber);
  }

  public loadMoreMovies(genreId: number, pageNumber: number) {
    // Get movies by genre id
    this.subscription.add( // <- changed here!
      this.movieService
        .getMoviesByGenre(genreId, pageNumber)
        .subscribe((response) => {
          this.movieResponse = response;
          this.movies.push(...(this.movieResponse?.results || []));
        })
    );
  }

  ngAfterViewInit() {
    this.subscription.add( // <- changed here!
      fromEvent(window, 'scroll')
        .pipe(
          startWith(0),
          map(() => window?.scrollY),
          distinctUntilChanged()
        )
        .subscribe((scrollPos: any) => {
          if (!this.movies?.length) {
            return;
          }
          if (
            Math.round(scrollPos + window.innerHeight) >=
              document.documentElement.scrollHeight &&
            this.pageNumber < this.maxPage
          ) {
            this.pageNumber++;
            this.loadMoreMovies(this.genreId, this.pageNumber);
          }
        })
    );
  }

  ngOnInit() {
    this.subscription.add( // <- changed here!
      this.activatedRoute.params.subscribe(() => {
        this.movies = [];
        this.getMoviesByGenre();
      })
    );
  }

  ngOnDestroy() {
    this.subscription.unsubscribe(); // <- changed here!
    this.movieService.defaultTitle = '';
  }
}

Другие вопросы по теме

Похожие вопросы