Как стать автором
Обновить

Angular пагинация

Уровень сложностиПростой

Недавно делал проект для портфолио и искал способ как сделать пагинацию. К сожалению, в сети советуют в основном использование сторонних библиотек. Я же захотел сделать без них.

Это не туториал, не учебник по TS/Angular/Rxjs. Это просто заметка о том как относительно просто реализовать пагинацию без сторонних зависимостей. Сработает не только с Angular-ом.

В примере я пользуюсь популярной заглушкой для разработки WebAPI dummyjson. Сервис позволяет отправлять параметры limit и skip для подгрузки данных порциями, что нам и нужно. Я делал сайт по поиску работы, поэтому и код покажу оттуда.

Работа этого варианта пагинации основана на IntersectionObserver. Этот "обсервер" следит за пересечением указанных элементов на странице или - что нам и интересно - за попаданием элемента на экран. То есть при попадании определённого элемента на экран(в моём случае, вы можете реализовать другое условие) будет срабатывать entry.isIntersecting в написанной директиве.

Собственно, вот директива, которая и будет править балом.

enter-the-viewport-notifier.directive.ts
@Directive({
  selector: '[appEnterTheViewportNotifier]',
})
export class EnterTheViewportNotifierDirective {
  // Издатель события, который сработает при попадании элемента с директивой на экран
  // Издаёт true, когда элемент появляется на экране и flase когда выходит с области видимости.
  @Output() visibilityChange = new EventEmitter<boolean>();
  private observer!: IntersectionObserver;
  
  // Ссылка на HTML элемент, содержащий директиву
  constructor(@Host() private elementRef: ElementRef) {}
  
  // Здесь мы создадим сам наблюдатель и настройки к нему.
  // А еще подпишемся на отлсеживание компонента.
  ngAfterViewInit(): void {
    const options = { root: null, rootMargin: '0px', threshold: 0.0 };
    this.observer = new IntersectionObserver(this.callback, options);
    this.observer.observe(this.elementRef.nativeElement);
  }

  ngOnDestroy() {
    this.observer.disconnect();
  }
  // Обыкновенный колбэк, который проверяет наличие на экране IntersectionObserver,
  // как только элемент попадёт на экран, сработает издаст true,
  // выйдет за пределы false.
  /**
   * Издаст событие при попадании элумента на экран.
   * @param entries
   * @param observer
   */
  private callback = (entries: any) => {
    entries.forEach((entry: any) => {
      this.visibilityChange.emit(entry.isIntersecting ? true : false);
    });
  };

На понадобится:

  • Модель данных. У меня это соискатель.

stored-user.interface.ts
export interface StoredUser {
  firstName: string;
  lastName: string;
  email: string;
  image?: string;
}

  • Сервис по загрузке данных. Позже мы на него подпишемся в компоненте.

applicants.service.ts
export class ApplicantsService {
  constructor(private httpClient: HttpClient) {}

  private URL: string = 'https://dummyjson.com/users';
  /**
   * Сервис для пагинации.
   * @param limit Кол-во загружаемых сущностей.
   * @param skip Кол-во пропускаемых сущностей.
   * @returns Загруженные сущности.
   */
  getUsers(limit: number, skip: number) {
    return this.httpClient.get<StoredUser>(
      this.URL + `?limit=${limit}&skip=${skip}`
    );
  }
}

  • Сам класс компонента.

applicants-list.component.ts
export class ApplicantsListComponent {
  constructor(
    private applicantsService: ApplicantsService
  ) {}
  users: StoredUser[] = []; // для хранения полученных данных
  
  // те самые "порции" данных. При желании их можно вынести во вью,
  // чтобы пользователь сам устанавливал их размер
  private skip: number = 0; 
  private limit = 10;

  // Здесь мы подписываемся на сервис, получаем загруженные данные,
  // и добавляем их к уже имеющимся. Именно добавляем, чтобы, если у вас 
  // данные отображаются в строку, при подгрузке эта строка не начиналась сначала,
  // а дополнялась.
  getAll(limit: number, skip: number) {
    this.applicantsService.getUsers(limit, skip).subscribe({
      next: (res: any) => {
        res['users'].forEach((element: StoredUser) => {
          this.users.push(element);
        });
      }
    });
  // Метод, который будет подгружать новые данные.
  /**
   * Обновляет параметры загрузки, загружает обновлённые данные
   * @param e Событие - срабатывает в конце списка.
   */
  onScroll(e: any) {
    if (e) {
      this.getAll(this.limit, this.skip);
      this.skip += this.limit;
      console.log(`skip - ${this.skip}`);
      console.log(`limit - ${this.limit}`);
    }
  }
}

  • Вьюха с отображением данных.

applicants-list.component.html
<div class="row">
    <div class="col-sm-3"
         *ngFor="let user of users">
        <div class="card"
             style="width: 18rem;">
            <img class="card-img-top"
                 src={{user.image}}
                 alt="Card image cap">
            <div class="card-body">
                <h4 class="card-title">{{getUserName(user.firstName, user.lastName)}}</h4>
                <h5 class="card-text">{{truncateAddress($any(user)['address']['address'], 20)}}</h5>
                <h5 class="card-text">{{truncateAddress($any(user)['address']['city'], 20)}}</h5>
                <div class="d-grid gap-2">
                    <button (click)="showUserInfo(user)"
                            data-bs-toggle="modal"
                            data-bs-target="#exampleModal"
                            class="btn btn-primary">Подробнее</button>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- САМОЕ ИНТЕРЕСНОЕ ТУТ -->
<!-- div издаёт событие, о том, что надо подгружать пользователей -->
<!-- добавим жёлтый цвет просто, чтобы видеть область на экране. Для теста -->
<div appEnterTheViewportNotifier
  style="width: 100%; height: 50px; background-color: yellow;"
     (visibilityChange)="onScroll($event)"></div>

По сути, везде где написано элемент появляется на экране следует понимать, что появление элемента на экране есть пересечение элемента с директивой с родительским элементом. Или с тем элементом, который указан в const options = { root: null, rootMargin: '0px', threshold: 0.0 }; где root это и есть элемент, с которым пересекается директива.

Теперь при прокрутке страницы вниз, будет появляться жёлтый div и с ним подгружаться новые данные, увеличиваться параметр skip на величину limit.

Надеюсь написано не слишком сумбурно, и кто-то воспользуется таким способом пагинации.

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.