Я работал над SPA с Angular 16, TypeScript и The Movie Database (TMDB).
Мне нужно сделать функцию «бесконечная прокрутка» на одном из компонентов.
Для этого у меня есть:
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 genreId = Number(this.activatedRoute.snapshot.paramMap.get('id'));
const maxPage: number = 10;
let pageNumber: number = 1;
let isLoading: boolean = false;
// 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 === genreId);
if (currentGenre) {
this.genreName = currentGenre.name || '';
this.movieService.defaultTitle = this.genreName;
}
}
});
// Get movies by genre id
this.movieService.getMoviesByGenre(genreId, pageNumber).subscribe((response) => {
this.movieResponse = response;
this.movies = this.movieResponse.results;
@HostListener('window:scroll', ['$event'])
onWindowScroll(event) {
if (window.innerHeight + window.scrollY >= document.body.offsetHeight &&! isLoading) {
if (pageNumber < maxPage) {
isLoading = true;
// Push into the movies array
this.movies?.push(...this.movies);
// Increment page number
pageNumber++;
isLoading = false;
}
}
}
})
}
ngOnInit() {
this.activatedRoute.params.subscribe(() => {
this.getMoviesByGenre();
});
}
ngOnDestroy() {
this.movieService.defaultTitle = '';
}
}
Я не ожидал, что мой метод @HostListener потерпит неудачу, но это произошло.
Выдает эту ошибку:
TS1146: Declaration expected.
@HostListener('window:scroll', ['$event'])
Я добавил сюда stackblitz.
getMoviesByGenre()?Есть ли реальная альтернатива использованию @HostListener?
Вы можете вручную добавить прослушиватель событий к элементу хоста, но это будет выполнять ту же работу, что и @HostListener. Есть ли причина, по которой onWindowScroll не может быть методом класса?
@DM Нет другой причины, по которой onWindowScroll не быть методом класса, кроме сохранения логики, связанной с массивом фильмов, в методе getMoviesByGenre(). Поэтому я предпочитаю именно так.
если вы сделаете это, вы продолжите добавлять прослушиватели событий, когда получите результаты. Ошибка возникает, потому что вы вызываете ее изнутри анонимной функции. Не думаете, что там можно использовать импорт? Возможно, объявите это глобально... но, опять же, вы не хотите продолжать добавлять прослушиватели событий.
почему бы не использовать cdk-virtual-scroll-viewport ?, как, например, ТАК, связанное с бесконечной прокруткой с помощью FormArray
@Eliseo Сделав это, составил список фильмов и вообще не рендерил: <cdk-virtual-scroll-viewport itemSize = "auto"><div *cdkVirtualFor = "let movie of movies" class = "col-xs-12 col-sm-6 col-lg-4 col-xl-3"><app-movie-card class = "movie card" [movie] = "movie" [showGenres] = "false"></app-movie-card></div></cdk-virtual-scroll-viewport>.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Спасибо, stackblitz заработал, код реализации смотрите ниже.
Как сделать бесконечную прокрутку в JS <- мне помогла статья
Мы можем просто вызвать вызов API при загрузке с номером страницы 1. Затем при каждой последующей прокрутке мы увеличиваем номер страницы, а затем снова выполняем вызов API, но мы помещаем новые результаты в массив, а не присваиваем их, это создает эффект бесконечной прокрутки!
ngAfterViewInit() {
fromEvent(window, 'scroll')
.pipe(
startWith(0),
map((x) => window?.scrollY),
distinctUntilChanged()
)
.subscribe((scrollPos: any) => {
if (!this.movies?.length) {
return;
}
console.info(scrollPos + window.innerHeight); //scrolled from top
console.info(document.documentElement.scrollHeight); //visible part of screen
if (
Math.round(scrollPos + window.innerHeight) >=
document.documentElement.scrollHeight &&
this.pageNumber < this.maxPage
) {
this.pageNumber++;
this.getMoviesByPage(this.genreId, this.pageNumber);
}
});
}
import { Component, EventEmitter, HostListener } 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 moviesBuffer: Movie[] | undefined = [];
public genreResponse!: GenreResponse;
public genres: Genre[] | undefined = [];
scrollCount = 10;
genreId!: number;
maxPage: number = 10;
pageNumber: number = 1;
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.getMoviesByPage(this.genreId, this.pageNumber);
}
getMoviesByPage(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((x) => window?.scrollY),
distinctUntilChanged()
)
.subscribe((scrollPos: any) => {
if (!this.movies?.length) {
return;
}
console.info(scrollPos + window.innerHeight); //scrolled from top
console.info(document.documentElement.scrollHeight); //visible part of screen
if (
Math.round(scrollPos + window.innerHeight) >=
document.documentElement.scrollHeight &&
this.pageNumber < this.maxPage
) {
this.pageNumber++;
this.getMoviesByPage(this.genreId, this.pageNumber);
}
});
}
ngOnInit() {
this.activatedRoute.params.subscribe(() => {
this.getMoviesByGenre();
});
}
ngOnDestroy() {
this.movieService.defaultTitle = '';
}
}
<ng-container *ngIf = "movieResponse">
<div class = "row grid">
<div
*ngFor = "let movie of movies"
class = "col-xs-12 col-sm-6 col-lg-4 col-xl-3"
>
<app-movie-card class = "movie card" [movie] = "movie"></app-movie-card>
</div>
</div>
</ng-container>
Нам нужно взять ViewChild прокручиваемого элемента div.
@ViewChild('scroller') scroller!: ElementRef<any>;
Затем в ngAfterViewInit мы можем использовать rxjs fromEvent для прослушивания прокрутки и применения необходимой вам логики.
ngAfterViewInit() {
if (scroller?.nativeElement) {
// for window or anything else
// fromEvent(window, 'scroll')
// for some div
fromEvent(scroller.nativeElement, 'scroll').pipe(
startWith(0),
map(x => window?.scrollY),
distinctUntilChanged(),
).subscribe((scrollPos: any) => {
// apply logic here!
});
}
}
Мне нужна вся логика, связанная с получением и добавлением массива movies в методе getMoviesByGenre().
Я добавил сюда stackblitz.
Они работают. Код не компилируется.
Отлично, я не видел использования переменной maxPage в скрипте. Я хочу, чтобы было загружено максимум 10 страниц фильмов.
По какой-то причине приложение никогда не загружает более 3 страниц фильмов (отфильтрованных по жанру), хотя есть и другие страницы (я проверил на themoviedb.org.)
Вы также обновили stackblitz?
По какой-то причине после того, как я посещаю страницу жанра (например, by-genre/16), приложение продолжает отправлять HTTP-запросы независимо от того, по какому маршруту я нахожусь.
@RazvanZamfir Вызовы API поступают из метода MovieCardComponentgetAllMovieGenres, не могли бы вы попробовать вернуть жанр также при получении списка фильмов, это решит вашу проблему, в основном это лучше делать из бэкэнда!
Я не понимаю, как я мог это сделать. :(
@RazvanZamfir это выполнимо, но решение работает с точки зрения внешнего интерфейса
Я почти уверен, что вы не можете использовать
@HostListenerвнутри метода. Я когда-либо видел это только в области классов. Таким образом, вы можете исправить это, повысивonWindowScrollдо метода класса и обработав случай, когда фильмы еще не загружены, внутри обработчика событий.