# #angular #typescript #google-cloud-firestore #pagination #vmware-clarity
Вопрос:
У меня есть сетка данных с пагинатором и возможностью выбора нужного пользователю размера страницы. Мои данные находятся в Google Firestore. Мне нужно ограничить данные на стороне сервера, так как база данных будет большой, и это повлияет на производительность. Но когда я ограничиваю данные, пейджинатор имеет доступ только к первым 25 записям, что означает, что пользователь не видит стрелок вперед или назад, которые позволили бы ему перейти на следующую страницу. Я работаю над этим уже несколько дней и не могу найти ответ. Поскольку мои данные из firestore, было бы слишком много времени, чтобы создать stackblitz и изменить все функции для работы с локальными данными. Есть идеи, что я делаю не так? Я думал, что моя проблема заключалась в том, что не хватало общего количества элементов, поэтому я создал функцию getLength (), чтобы справиться с этим, но это не меняет поведения. То, что я вижу внизу страницы, заключается в следующем: вместо этого это мой компонент
import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
import {AngularFirestore} from '@angular/fire/firestore';
import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs';
import {distinctUntilChanged, map, switchMap, tap} from 'rxjs/operators';
import {Caregiver, CaregiverState, Paginator} from '../caregiver.interface';
import {environment} from '../../../environments/environment';
import {AgencyObj} from '../../agencies/agencies/Agency.interface';
import {ClrDatagridFilterInterface, ClrDatagridStateInterface} from '@clr/angular';
import {ExportCsvService} from 'src/app/shared/services/export-csv.service';
import {DateRangeFilter} from "../../transactions/transactions/transactions.component";
import {Transaction} from "../../transactions/transactions/Transaction.interface";
import firebase from "firebase";
import Timestamp = firebase.firestore.Timestamp;
let STATE: CaregiverState = {
caregivers: [],
filters: [],
pagination: {
after: undefined,
before: undefined,
page: 1,
rows: 25,
limit: 25
},
};
@Component({
selector: 'app-caregivers',
templateUrl: './caregivers.component.html',
styleUrls: ['./caregivers.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CaregiversComponent implements OnInit {
store = new BehaviorSubject(STATE);
public state$ = this.store.asObservable();
api_root = environment.API_ROOT;
agencyObj = AgencyObj;
onBoardingCompletedFilter: OnBoardingCompletedFilter;
nameFilter: NameFilter;
nameFilterValues = ['xxx', 'yyy', 'zzz']
phoneFilter: PhoneFilter;
caregiverCodeFilter: PhoneFilter;
dateCreatedFilter: DateRangeFilter
agencyName: string;
agencyId: string;
rows = 25;
caregiverLength: number;
pagination$ = this.state$.pipe(
map((state) => state.pagination),
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
);
filters$ = this.state$.pipe(
map((state) => state.filters),
distinctUntilChanged()
);
caregivers$: Observable<Caregiver[]> = combineLatest([this.pagination$, this.filters$]).pipe(
switchMap(([pagination, filters]) => {
return this.afs
.collection<Caregiver>('caregivers', (ref) => {
let query: any = ref;
if (filters?.length) {
for (const filter of filters) {
if (filter.property === "onboardingCompleted") {
const value = JSON.parse(filter.value?.toString());
query = query.where(filter.property, "==", value)
}
if (filter.property === 'agency.agencyId') {
query = query.where(filter.property, 'in', filter.value)
}
if (filter.property === 'phoneNumbers') {
console.log(filter.property);
query = query.where(filter.property, 'array-contains', filter.value)
}
if (filter.property === 'caregiverCode' amp;amp; filter.value) {
console.log(filter.property);
query = query.where(filter.property, '==', filter.value)
}
if (filter.property === 'createdAt') {
if (filter.value) {
const fromDate = new Date(filter.value.replace(/-/g, '/'));
console.log(fromDate);
query = query.where(
filter.property,
filter.operator,
fromDate
);
}
if (filter.value2) {
const toDate = new Date(filter.value2.replace(/-/g, '/'));
toDate.setDate(toDate.getDate() 1);
console.log(toDate);
query = query.where(
filter.property,
filter.operator2,
toDate
);
}
}
}
}
if (pagination.after) {
const state = this.store.getValue();
const pagination: Paginator = {
after: state.caregivers[state.caregivers.length - 1],
before: undefined,
limit: state.pagination.limit,
}
if (this.dateCreatedFilter.value !== null) {
query = query
.orderBy('createdAt', 'asc')
.orderBy('firstName', 'asc')
.orderBy('caregiverId')
.startAfter(pagination.after.firstName,
pagination.after.caregiverId)
} else {
query = query
.orderBy('firstName', 'asc')
.orderBy('caregiverId')
.startAfter(
pagination.after.firstName,
pagination.after.caregiverId
)
}
} else if (pagination.before) {
const state = this.store.getValue();
const pagination: Paginator = {
after: undefined,
before: state.caregivers[0],
limit: state.pagination.limit,
}
if (this.dateCreatedFilter.value !== null) {
query = query
.orderBy('createdAt', 'asc')
.orderBy('firstName', 'asc')
.orderBy('caregiverId')
.endBefore(
pagination.before.firstName,
pagination.before.caregiverId,
)
} else {
query = query
.orderBy('firstName', 'asc')
.orderBy('caregiverId')
.endBefore(
pagination.before.firstName,
pagination.before.caregiverId,
)
}
} else {
let pagination = this.store.getValue().pagination;
if (this.dateCreatedFilter.value !== null) {
query = query.orderBy('createdAt', 'asc')
.orderBy('firstName', 'asc')
.limit(pagination.limit)
} else {
query = query.orderBy('firstName', 'asc')
.limit(pagination.limit)
}
}
// this.updateState({...STATE, pagination})
return query;
})
.valueChanges()
.pipe(
tap(console.log),
tap((caregiversArray) => {
const caregivers = caregiversArray;
this.updateState({...STATE, caregivers});
})
);
})
);
constructor(public afs: AngularFirestore, private csvExport: ExportCsvService) {
this.onBoardingCompletedFilter = new OnBoardingCompletedFilter();
}
ngOnInit(): void {
this.nameFilter = new NameFilter();
this.nameFilter.value = [];
this.phoneFilter = new PhoneFilter();
this.phoneFilter.value = '';
this.caregiverCodeFilter = new PhoneFilter();
this.caregiverCodeFilter.property = 'caregiverCode';
this.dateCreatedFilter = new DateRangeFilter();
this.dateCreatedFilter.value = null;
this.dateCreatedFilter.value2 = null;
this.dateCreatedFilter.property = 'createdAt';
this.dateCreatedFilter.operator = '>=';
this.dateCreatedFilter.operator2 = '<=';
this.getLength();
}
resetDateRangeFilter() {
this.dateCreatedFilter.value = null;
this.dateCreatedFilter.value2 = null;
this.dateCreatedFilter.changes.next(null);
}
updateNameFilter(event) {
if (event.target.checked) {
if (this.nameFilter.value.indexOf(event.target.value) == -1) {
this.nameFilter.value.push(event.target.value);
}
} else {
const findFilterIndex = this.nameFilter.value.indexOf((event.target.value));
this.nameFilter.value.splice(findFilterIndex, 1);
}
let filters = this.store.getValue()?.filters;
if (!filters?.length) filters = [];
const nameFilterIndex = filters.findIndex(f => f.property === this.nameFilter.property);
if (nameFilterIndex !== -1) filters[nameFilterIndex] = this.nameFilter;
else filters.push({value: this.nameFilter.value, property: this.nameFilter.property});
this.updateState({...STATE, filters})
this.nameFilter.changes.next(true);
}
updatePhoneFilter(event) {
this.phoneFilter.value = event.target.value?.replace(/[^d]/g, '')
let filters = this.store.getValue()?.filters;
if (!filters?.length) filters = [];
filters.push({
value: this.phoneFilter.value,
property: this.phoneFilter.property
})
this.updateState({...STATE, filters})
this.nameFilter.changes.next(true);
}
updateCaregiverCodeFilter(event) {
this.caregiverCodeFilter.value = event.target.value
let filters = this.store.getValue()?.filters;
if (!filters?.length) filters = [];
filters.push({
value: this.caregiverCodeFilter.value,
property: this.caregiverCodeFilter.property
})
this.updateState({...STATE, filters})
this.nameFilter.changes.next(true);
}
updateDateFilter(event, direction) {
if (direction === 'from')
this.dateCreatedFilter.value = event.target.value;
if (direction === 'to')
this.dateCreatedFilter.value2 = event.target.value;
let filters = this.store.getValue()?.filters;
if (!filters?.length) filters = [];
const dateRangeFilterindex = filters.findIndex(
(f) => f.property === this.dateCreatedFilter.property
);
if (dateRangeFilterindex !== -1)
filters[dateRangeFilterindex] = this.dateCreatedFilter;
else
filters.push({
value: this.dateCreatedFilter.value,
value2: this.dateCreatedFilter.value2,
property: this.dateCreatedFilter.property,
});
this.updateState({...STATE, filters});
}
toggleOnboardingCompletedSelection(event: any): void {
if (event.target.value === 'undefined')
this.onBoardingCompletedFilter.value = 'true';
else
this.onBoardingCompletedFilter.value =
event.target.value === 'true' ? 'false' : 'true';
this.onBoardingCompletedFilter.changes.next(
this.onBoardingCompletedFilter.value
);
}
resetToggleOnboardingCompletedSelection() {
this.onBoardingCompletedFilter.value = 'undefined';
this.onBoardingCompletedFilter.changes.next('undefined');
}
onTableRefresh(state: ClrDatagridStateInterface<Caregiver>): void {
console.log(state);
this.updateState({...STATE, filters: state.filters})
}
private updateState(state: CaregiverState): void {
this.store.next((STATE = state));
}
async downloadCsv() {
const dataSnapshots = await this.afs.collection<Caregiver>('caregivers').get().toPromise();
const data = [];
for (const snapsot of dataSnapshots.docs) {
data.push(snapsot.data())
}
const header = ['caregiverId', 'firstName', 'lastName', 'agency.name', 'createdAt', 'caregiverCode', 'phoneNumbers'];
const filename = 'caregivers';
this.csvExport.downloadCsv(data, header, filename);
}
async getLength() {
const dataSnapshots = await this.afs.collection<Caregiver>('caregivers').get().toPromise();
this.caregiverLength = dataSnapshots.docs.length;
console.log(this.caregiverLength);
}
}
export class OnBoardingCompletedFilter implements ClrDatagridFilterInterface<Caregiver> {
changes = new Subject<any>();
value = "undefined";
property = 'onboardingCompleted';
isActive(): boolean {
return this.value !== "undefined";
}
accepts(caregiver: Caregiver) {
return this.value !== "undefined" amp;amp; JSON.parse(this.value) === caregiver.onboardingCompleted;
}
}
export class NameFilter implements ClrDatagridFilterInterface<Caregiver> {
changes = new Subject<any>();
value = [''];
property = 'agency.agencyId';
isActive(): boolean {
return this.value.length > 0;
}
accepts(caregiver: Caregiver) {
return true;
}
}
export class PhoneFilter implements ClrDatagridFilterInterface<any> {
changes = new Subject<any>();
value = '';
property = 'phoneNumbers';
isActive(): boolean {
return this.value !== '';
}
accepts(any) {
return true
}
}
export class CreatedDateFilter implements ClrDatagridFilterInterface<Transaction> {
changes = new Subject<any>();
value: Timestamp | string;
value2: Timestamp | string;
property = '';
operator = '>=';
operator2 = '<=';
isActive(): boolean {
return this.value !== null amp;amp; this.value2 !== null;
}
accepts(transaction: Transaction) {
return true;
}
}
и мой шаблон
<clr-dg-footer>
<div class="clr-row clr-justify-content-between">
<cds-icon class="btn-icon" style="margin: 5px 0 0 30px; cursor: pointer;" shape="xls-file" size="md"
status="success" (click)="downloadCsv()"></cds-icon>
<div>
<clr-dg-pagination
#pagination
[clrDgPageSize]="rows"
[clrDgTotalItems] = pagination.totalItems>
<clr-dg-page-size [clrPageSizeOptions]="[25, 50, 100, 500, 1000]"
>records per page
</clr-dg-page-size>
{{ pagination.firstItem 1 }} - {{ pagination.lastItem 1 }} of
{{pagination.totalItems }} records
</clr-dg-pagination>
</div>
</div>
</clr-dg-footer>
если я изменю свой html следующим образом:{{ разбиение на страницы.firstItem 1 }} — {{ разбиение на страницы.Последнее число 1 }}
записей {{Продолжительность ухода }}
Я получаю правильное количество записей вместо 25, но я не получаю стрелок, которые нужны пользователю для перехода на следующую или pr-страницу. Когда я отлаживаю, я вижу, что totalItems, которая является встроенной переменной пагинатора, изменяется с правильной длины массива на 25, что является значением строк.
Комментарии:
1. при дальнейшем исследовании, похоже, что код в моем html запускает встроенный пагинатор, который не работает с данными firestore. Мне все еще нужно проверить это дальше, но вполне может быть проблема