Tenho trabalhado em um SPA com Angular 16, TypeScript e The Movie Database (TMDB).
Encontrei um problema após o desenvolvimento de um recurso de "rolagem infinita".
No MoviesByGenre
componente eu tenho:
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 = '';
}
}
No MovieService
serviço:
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}`);
}
}
A cada rolagem até o final da página do by-genre/:id
percurso, são carregados mais 20 filmes, com limite definido em 10 páginas (200 filmes).
O problema
O problema é que mesmo em outras rotas, como a rota de detalhes do filme, uma chamada de API é feita ao rolar até o final da página e mostra o botão giratório de carregamento, para o qual usei uma técnica de interceptador http , desnecessariamente.
O objetivo
O objetivo é tornar a rolagem infinita específica para o MoviesByGenre
componente:
Para isso, adicionei um booleano ao MoviesByGenre
componente e utilizei-o da seguinte forma:
public isLoadMore: boolean = true;
if (this.isLoadMore) {
this.loadMoreMovies(this.genreId, this.pageNumber);
}
Mas as solicitações ainda são feitas em outros componentes ao rolar até o final da página.
Parece que falhei em identificar a causa do problema e em fornecer uma solução viável .
Questões
- O que causa esse problema?
- Qual é a maneira mais confiável de consertar isso?
Temos que
unsubscribe
recorrer às assinaturas ativas para resolver esse problema, quando o componente é destruído, isso não significa necessariamente que as assinaturas foram destruídas, temos queadd
ligar todas elassubscription
e chamar ounsubscribe
método onngOnDestroy
do componente para eliminar todas as assinaturas!É uma boa prática adicionar todas as assinaturas a uma propriedade de assinatura e, em seguida, cancelá-las completamente!