Как мне использовать @HostListener внутри метода в этом приложении Angular 16?

Я работал над 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.

Вопросы

  1. Что я делаю не так?
  2. Есть ли реальная альтернатива использованию getMoviesByGenre()?

Я почти уверен, что вы не можете использовать @HostListener внутри метода. Я когда-либо видел это только в области классов. Таким образом, вы можете исправить это, повысив onWindowScroll до метода класса и обработав случай, когда фильмы еще не загружены, внутри обработчика событий.

D M 09.05.2024 16:19

Есть ли реальная альтернатива использованию @HostListener?

Razvan Zamfir 09.05.2024 16:27

Вы можете вручную добавить прослушиватель событий к элементу хоста, но это будет выполнять ту же работу, что и @HostListener. Есть ли причина, по которой onWindowScroll не может быть методом класса?

D M 09.05.2024 16:31

@DM Нет другой причины, по которой onWindowScroll не быть методом класса, кроме сохранения логики, связанной с массивом фильмов, в методе getMoviesByGenre(). Поэтому я предпочитаю именно так.

Razvan Zamfir 10.05.2024 08:07

если вы сделаете это, вы продолжите добавлять прослушиватели событий, когда получите результаты. Ошибка возникает, потому что вы вызываете ее изнутри анонимной функции. Не думаете, что там можно использовать импорт? Возможно, объявите это глобально... но, опять же, вы не хотите продолжать добавлять прослушиватели событий.

browsermator 10.05.2024 22:07

почему бы не использовать cdk-virtual-scroll-viewport ?, как, например, ТАК, связанное с бесконечной прокруткой с помощью FormArray

Eliseo 15.05.2024 12:22

@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-sc‌​roll-viewport>.

Razvan Zamfir 15.05.2024 15:29
Поведение ключевого слова "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
7
251
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Спасибо, 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 = '';
  }
}

HTML

<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>

Демо-версия Stackblitz


Нам нужно взять 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().

Razvan Zamfir 09.05.2024 17:28

Я добавил сюда stackblitz.

Razvan Zamfir 10.05.2024 09:19

Они работают. Код не компилируется.

Razvan Zamfir 10.05.2024 14:03

Отлично, я не видел использования переменной maxPage в скрипте. Я хочу, чтобы было загружено максимум 10 страниц фильмов.

Razvan Zamfir 13.05.2024 13:59

По какой-то причине приложение никогда не загружает более 3 страниц фильмов (отфильтрованных по жанру), хотя есть и другие страницы (я проверил на themoviedb.org.)

Razvan Zamfir 15.05.2024 10:47

Вы также обновили stackblitz?

Razvan Zamfir 15.05.2024 14:09

По какой-то причине после того, как я посещаю страницу жанра (например, by-genre/16), приложение продолжает отправлять HTTP-запросы независимо от того, по какому маршруту я нахожусь.

Razvan Zamfir 16.05.2024 09:54

@RazvanZamfir Вызовы API поступают из метода MovieCardComponentgetAllMovieGenres, не могли бы вы попробовать вернуть жанр также при получении списка фильмов, это решит вашу проблему, в основном это лучше делать из бэкэнда!

Naren Murali 17.05.2024 21:39

Я не понимаю, как я мог это сделать. :(

Razvan Zamfir 17.05.2024 22:36

@RazvanZamfir это выполнимо, но решение работает с точки зрения внешнего интерфейса

Naren Murali 17.05.2024 22:47

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