От автора: при работе с большим количеством контента может оказаться полезным добавить на ваш сайт функцию бесконечной прокрутки. Когда я говорю «бесконечная прокрутка», я имею в виду страницу, добавляющую новый контент, пока пользователь продолжает прокручивать ее, создавая иллюзию бесконечной прокрутки страницы. Многие веб-сайты используют эту функцию, и она может быть гибкой альтернативой нумерации страниц.
Несмотря на то, что существует множество различных способов реализации этого, давайте рассмотрим, как мы можем сделать это с помощью Angular Component Dev Kit (CDK).
Настройка
Начнем с добавления в проект пакета @angular/cdk:
$ npm install @angular/cdk
Чтобы использовать функции бесконечной прокрутки в этом пакете, импортируйте ScrollingModule в app.module.ts:
import { ScrollingModule} from '@angular/cdk/scrolling';
Затем добавьте его в импорт:
imports: [ ScrollingModule ]
Теперь вы готовы начать!
Реализация бесконечной прокрутки
Мы собираемся создать компонент, который отображает случайные исторические события. Когда пользователь достигнет конца прокрутки, приложение загрузит следующие факты. Для целей этого руководства я не буду вдаваться в детали построения сервиса.
Пока предположим, что у нас есть FactService, который предоставляет только следующую функцию:
getRandomFact();
Мы будем извлекать 10 фактов за раз, и каждый раз, когда пользователь прокручивает страницу до конца, мы запрашиваем еще 10 фактов.
FactScrollerComponent
Создайте новый компонент, который будет действовать как бесконечный скроллер. Я вызываю FactScrollerComponent. Вы можете использовать для этого Angular CLI:
$ ng g component fact-scroller
Убедитесь, что новый компонент импортирован в app.module.ts и добавлен в объявления:
import { ScrollingModule } from '@angular/cdk/scrolling'; declarations: [ FactScrollerComponent ]
В fact-scroller.component.html мы создадим:
<cdk-virtual-scroll-viewport itemSize="100"> <li *cdkVirtualFor="let fact of dataSource"> <!-- Print stuff here --> </li> </cdk-virtual-scroll-viewport>
Здесь мы используем наш виртуальный скроллер cdk-virtual-scroll-viewport. В нем мы перебираем через цикл элементаы, используя *cdkVirtualFor, что аналогично использованию *ngFor.
Чтобы компонент правильно определил размер внутреннего скроллера, нам нужно сообщить скроллеру, какой будет высота каждого элемента (в пикселях). Это делается с использованием директивы itemSize. Таким образом, itemSize=»100″ означает, что элемент в списке будет иметь высоту 100px.
Мы также указали скроллеру извлечь данные dataSource, которых еще нет, поэтому нужно создать их сейчас.
Пользовательский FactsDataSource
В файле fact-scroller.component.ts нам нужно определить, как выглядит источник данных. Для этого мы расширим класс DataSource в @angular/cdk/collections. Вот как выглядит источник данных:
import { CollectionViewer, DataSource } from '@angular/cdk/collections'; export interface Fact { text?: string; date?: string; } export class FactsDataSource extends DataSource<Fact | undefined> { private cachedFacts = Array.from<Fact>({ length: 0 }); private dataStream = new BehaviorSubject<(Fact | undefined)[]>(this.cachedFacts); private subscription = new Subscription(); constructor(private factService: FactService) { super(); } connect(collectionViewer: CollectionViewer): Observable<(Fact | undefined)[] | ReadonlyArray<Fact | undefined>> { this.subscription.add(collectionViewer.viewChange.subscribe(range => { // Update the data })); return this.dataStream; } disconnect(collectionViewer: CollectionViewer): void { this.subscription.unsubscribe(); } }
Здесь много кода, так что давайте разберемся с этим по порядку. Сначала мы определяем модель Fact, которая будет определять структуру данных.
Внутри FactsDataSource нам нужно реализовать две функции: connect() и disconnect(). Источник данных подписывается на любые изменения в средстве просмотра коллекции (например, прокрутки пользователя), а затем выполняет действие и возвращает поток данных. Мы указали источнику данных, чтобы он получал следующие данные, когда мы достигнем конца списка.
Мы также объявили три переменные-члена:
cachedFacts: кэшированные результаты,
dataStream: RxJS BehaviorSubject для распространения изменений в наших кэшированных результатах, и
subscription: подписка для прослушивания изменений коллекции представлений.
Давайте определим несколько вспомогательных функций в этом классе:
private pageSize = 10; private lastPage = 0; private _fetchFactPage(): void { for (let i = 0; i < this.pageSize; ++i) { this.factService.getRandomFact().subscribe(res => { this.cachedFacts = this.cachedFacts.concat(res); this.dataStream.next(this.cachedFacts); }); } } private _getPageForIndex(i: number): number { return Math.floor(i / this.pageSize); }
Я устанавливаю размер страницы — 10, что означает, что я хочу получить 10 фактов за раз. Я также собираюсь отслеживать последнюю загруженную страницу.
_fetchFactPage() просто выполняет вызов к сервису, чтобы получить некоторые факты, которые затем добавляются в кеш.
_getPageForIndex() преобразует индекс строки в значение страницы (или пачки).
Собрав все это вместе, мы можем определить, как список будет обновляться в обратном вызове подписки:
connect(collectionViewer: CollectionViewer): Observable<(Fact | undefined)[] | ReadonlyArray<Fact | undefined>> { this.subscription.add(collectionViewer.viewChange.subscribe(range => { const currentPage = this._getPageForIndex(range.end); if (currentPage > this.lastPage) { this.lastPage = currentPage; this._fetchFactPage(); } })); return this.dataStream; }
Мы также хотим, чтобы с самого начала у нас уже были некоторые данные, для этого мы можем вызвать функцию fetch в конструкторе:
constructor(private factService: FactService) { super(); // Start with some data. this._fetchFactPage(); }
Наш пользовательский источник данных теперь должен привести нас туда, куда нам нужно. Последняя часть, чтобы собрать все это вместе, это добавить новый источник данных в компонент.
@Component({ selector: 'app-fact-scroller', templateUrl: './fact-scroller.component.html', styleUrls: ['./fact-scroller.component.scss'] }) export class FactScrollerComponent { dataSource: FactsDataSource; constructor(private factService: FactService) { this.dataSource = new FactsDataSource(factService); } }
И все готово! С этого момента все форматируется. Я переписал HTML, чтобы вывести факты таким образом:
<cdk-virtual-scroll-viewport itemSize="100" class="fact-scroll-viewport"> <li *cdkVirtualFor="let fact of dataSource"> <div *ngIf="fact" class="fact-item"> <div class="fact-date">{{ fact.year }}</div> <div class="fact-text">{{ fact.text }}</div> </div> <div *ngIf="!fact"> Loading ... </div> </li> </cdk-virtual-scroll-viewport>
@Component({ selector: 'app-fact-scroller', templateUrl: './fact-scroller.component.html', styleUrls: ['./fact-scroller.component.scss'] }) export class FactScrollerComponent { dataSource: FactsDataSource; constructor(private factService: FactService) { this.dataSource = new FactsDataSource(factService); } }
Удачной прокрутки!
Автор: Chris Engelsma
Источник: https://alligator.io
Редакция: Команда webformyself.