Order and pagination in travel list

This commit is contained in:
Eneko Nieto
2021-01-24 01:34:17 +01:00
parent 4957f8f074
commit 94eba7aefa
26 changed files with 38 additions and 729 deletions

View File

@@ -1,3 +1,10 @@
export const API_BASE_URL = 'http://localhost:8080';
export const API_RELATIVE_PATH = '/api';
export const API_URL = API_BASE_URL + API_RELATIVE_PATH;
export const API_BASE_URL = 'http://localhost:8080/api';
const PUBLIC_API_RELATIVE_PATH = '/public';
export const PUBLIC_API_URL = API_BASE_URL + PUBLIC_API_RELATIVE_PATH;
const USER_API_RELATIVE_PATH = '/user';
export const USER_API_URL = API_BASE_URL + USER_API_RELATIVE_PATH;
const TRAVEL_API_RELATIVE_PATH = '/travel';
export const TRAVEL_API_URL = API_BASE_URL + TRAVEL_API_RELATIVE_PATH;

View File

@@ -6,8 +6,7 @@ import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { APP_ROUTES } from './app.routes';
import { API_URL } from './app.config';
import { FlightHistoryComponent } from './flight-history/flight-history.component';
import { USER_API_URL, TRAVEL_API_URL } from './app.config';
import { HomeComponent } from './home/home.component';
import { SharedModule } from './shared/shared.module';
import { RouterModule, ExtraOptions } from '@angular/router';
@@ -37,7 +36,7 @@ const ROUTING_OPTIONS: ExtraOptions = {
SharedModule.forRoot(),
OAuthModule.forRoot({
resourceServer: {
allowedUrls: [API_URL],
allowedUrls: [USER_API_URL, TRAVEL_API_URL],
sendAccessToken: true,
},
}),
@@ -46,21 +45,19 @@ const ROUTING_OPTIONS: ExtraOptions = {
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatProgressSpinnerModule
MatProgressSpinnerModule,
],
declarations: [
AppComponent,
HomeComponent,
FlightHistoryComponent,
HeaderComponent,
TravelListComponent
TravelListComponent,
],
providers: [
// (useHash) ? { provide: LocationStrategy, useClass: HashLocationStrategy } : [],
// {provide: AuthConfig, useValue: authConfig },
// { provide: OAuthStorage, useValue: localStorage },
// { provide: ValidationHandler, useClass: JwksValidationHandler },
// { provide: BASE_URL, useValue: 'http://localhost:8080' }
],
bootstrap: [AppComponent],
})

View File

@@ -1,6 +1,5 @@
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { FlightHistoryComponent } from './flight-history/flight-history.component';
export let APP_ROUTES: Routes = [
{
@@ -12,18 +11,6 @@ export let APP_ROUTES: Routes = [
path: 'home',
component: HomeComponent
},
{
path: 'flight-booking',
loadChildren: () =>
import('./flight-booking/flight-booking.module').then(
mod => mod.FlightBookingModule
)
},
{
path: 'history',
component: FlightHistoryComponent,
outlet: 'aux'
},
{
path: '**',
redirectTo: 'home'

View File

@@ -1,7 +0,0 @@
export interface Flight {
id: number; // int + double
from: string;
to: string;
date: string;
// JSON: ISO-String 2016-12-24T17:00:00.000+01:00
}

View File

@@ -1,17 +0,0 @@
<div
style="padding:20px;"
[ngStyle]="{ 'background-color': selected ? 'orange' : 'lightsteelblue' }"
>
<h2>{{ item.from }} - {{ item.to }}</h2>
<p>Flugnr. #{{ item.id }}</p>
<p>Datum: {{ item.date | date: 'dd.MM.yyyy HH:mm' }}</p>
<p>
<input
type="button"
value="Auswählen"
class="btn btn-default"
(click)="select()"
/>
</p>
</div>

View File

@@ -1,16 +0,0 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Flight } from '../../entities/flight';
@Component({
selector: 'alt-flight-card',
templateUrl: 'alt-flight-card.component.html'
})
export class AltFlightCardComponent {
@Input() item: Flight;
@Input() selected: boolean;
@Output() selectedChange = new EventEmitter<boolean>();
select() {
this.selectedChange.emit(true);
}
}

View File

@@ -1,26 +0,0 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Flight } from '../../entities/flight';
@Component({
selector: 'flight-list',
template: `
<div class="row">
<div *ngFor="let f of flights" class="col-sm-6 col-md-4 col-lg-3 ">
<alt-flight-card
[item]="f"
[selected]="f == selectedFlight"
(selectedChange)="change(f)"
>
</alt-flight-card>
</div>
</div>
`
})
export class FlightListComponent {
@Input() flights: Flight[] = [];
@Input() selectedFlight: Flight;
@Output() selectedFlightChange = new EventEmitter();
change(f: Flight) {
this.selectedFlightChange.emit(f);
}
}

View File

@@ -1,10 +0,0 @@
<div class="col-sm-3">
<ul class="nav nav-pills nav-stacked" style="margin-top:20px;">
<li><a [routerLink]="['./flight-search']">Flug auswählen</a></li>
<li><a [routerLink]="['./passenger-search']">Passagier auswählen</a></li>
</ul>
</div>
<div class="col-md-9">
<router-outlet></router-outlet>
</div>

View File

@@ -1,8 +0,0 @@
import { Component } from '@angular/core';
import { FlightService } from './services/flight.service';
@Component({
selector: 'flight-booking',
templateUrl: './flight-booking.component.html'
})
export class FlightBookingComponent {}

View File

@@ -1,37 +0,0 @@
import { NgModule } from '@angular/core';
import { FlightSearchComponent } from './flight-search/flight-search.component';
import { FlightCardComponent } from './flight-card/flight.card.component';
import { AltFlightCardComponent } from './alt-flight-card/alt-flight.card.component';
import { FlightListComponent } from './alt-flight-card/flight-list';
import { PassengerSearchComponent } from './passenger-search/passenger-search.component';
import { FlightEditComponent } from './flight-edit/flight-edit.component';
import { FlightSearchReactiveComponent } from './flight-search-reactive/flight-search-reactive.component';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { SharedModule } from '../shared/shared.module';
import { FlightBookingRouterModule } from './flight-booking.routes';
import { FlightBookingComponent } from './flight-booking.component';
import { FlightService } from './services/flight.service';
@NgModule({
imports: [
CommonModule, // ngFor
FormsModule,
ReactiveFormsModule,
SharedModule,
FlightBookingRouterModule
],
declarations: [
FlightSearchComponent,
FlightCardComponent,
AltFlightCardComponent,
FlightListComponent,
FlightSearchReactiveComponent,
PassengerSearchComponent,
FlightEditComponent,
FlightBookingComponent
],
providers: [FlightService],
exports: []
})
export class FlightBookingModule {}

View File

@@ -1,34 +0,0 @@
import { Routes, RouterModule } from '@angular/router';
import { FlightSearchComponent } from './flight-search/flight-search.component';
import { PassengerSearchComponent } from './passenger-search/passenger-search.component';
import { FlightEditComponent } from './flight-edit/flight-edit.component';
import { FlightBookingComponent } from './flight-booking.component';
import { AuthGuard } from '../shared/auth/auth.guard';
import { LeaveComponentGuard } from '../shared/deactivation/LeaveComponentGuard';
const FLIGHT_BOOKING_ROUTES: Routes = [
{
path: '',
component: FlightBookingComponent,
canActivate: [AuthGuard],
children: [
{
path: 'flight-search',
component: FlightSearchComponent
},
{
path: 'passenger-search',
component: PassengerSearchComponent
},
{
path: 'flight-edit/:id',
component: FlightEditComponent,
canDeactivate: [LeaveComponentGuard]
}
]
}
];
export let FlightBookingRouterModule = RouterModule.forChild(
FLIGHT_BOOKING_ROUTES
);

View File

@@ -1,21 +0,0 @@
<div
style="padding:20px;"
[ngStyle]="{
'background-color': selectedItem == item ? 'orange' : 'lightsteelblue'
}"
>
<h2>{{ item.from }} - {{ item.to }}</h2>
<p>Flugnr. #{{ item.id }}</p>
<p>Datum: {{ item.date | date: 'dd.MM.yyyy HH:mm' }}</p>
<p>
<input
type="button"
value="Auswählen"
class="btn btn-default"
(click)="select()"
/>
<ng-content></ng-content>
</p>
</div>

View File

@@ -1,16 +0,0 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Flight } from '../../entities/flight';
@Component({
selector: 'flight-card',
templateUrl: './flight-card.component.html'
})
export class FlightCardComponent {
@Input() item: Flight;
@Input() selectedItem: Flight;
@Output() selectedItemChange = new EventEmitter<Flight>();
select() {
this.selectedItemChange.emit(this.item);
}
}

View File

@@ -1,57 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
template: `
<h1>Flight Edit!</h1>
<p>Hier könnte auch der Datensatz mit der Id {{ id }} stehen!</p>
<div *ngIf="exitWarning.show" class="alert alert-warning">
<div>
Daten wurden nicht gespeichert! Trotzdem Maske verlassen?
</div>
<div>
<a
href="javascript:void(0)"
(click)="decide(true)"
class="btn btn-danger"
>Ja</a
>
<a
href="javascript:void(0)"
(click)="decide(false)"
class="btn btn-default"
>Nein</a
>
</div>
</div>
`
})
export class FlightEditComponent implements OnInit {
public id: string;
constructor(private route: ActivatedRoute) {
route.params.subscribe(p => {
this.id = p['id'];
});
}
ngOnInit() {}
exitWarning = {
show: false,
resolve: null
};
decide(decision: boolean) {
this.exitWarning.show = false;
this.exitWarning.resolve(decision);
}
canDeactivate() {
this.exitWarning.show = true;
return new Promise(resolve => {
this.exitWarning.resolve = resolve;
});
}
}

View File

@@ -1,11 +0,0 @@
input.ng-valid {
border-left-style: solid;
border-left-color: forestgreen;
border-left-width: 5px;
}
input.ng-invalid {
border-left-style: solid;
border-left-color: hotpink;
border-left-width: 5px;
}

View File

@@ -1,94 +0,0 @@
<h1>Flight Search (Reactive) !</h1>
<!--
round-trip
-->
<form novalidate [formGroup]="filter">
<h2>Dynamisches Formular</h2>
<div *ngFor="let item of formDesc">
<label>{{ item.label }}</label>
<input [formControlName]="item.name" class="form-control" />
</div>
<h2>Statisches Formular</h2>
<div class="form-group">
<label>From</label>
<input class="form-control" formControlName="from" />
<div *ngIf="!filter.controls.from.valid && !filter.controls.from.pending">
Validierungsfehler. Bitte prüfen Sie Ihre Eingaben.
</div>
<div *ngIf="filter.controls.from.hasError('city')">
Diese Stadt wird nicht angefolgen
</div>
<div *ngIf="filter.controls.from.hasError('required')">
Dieses Feld ist ein Pflichtfeld
</div>
</div>
<div class="form-group">
<label>To</label>
<input class="form-control" formControlName="to" name="to" />
</div>
<div class="form-group">
<button
class="btn btn-primary"
name="btnSearch"
[disabled]="!filter.valid"
(click)="search()"
>
Search
</button>
</div>
</form>
<!--
[ngClass]="{ 'active': f == selectedFlight }"
-->
<!--
<table class="table table-striped">
<tr *ngFor="let f of flights" [ngStyle]="{ 'background-color': (f == selectedFlight) ? 'orange': '' } ">
<td>{{f.id}}</td>
<td>{{f.from | city:'short' }}</td>
<td>{{f.to | city:'long' }}</td>
<td>{{f.date | date:'dd.MM.yyyy HH:mm'}}</td>
<td><a (click)="select(f)">Auswählen</a></td>
</tr>
</table>
-->
<div class="row">
<div *ngFor="let f of flights" class="col-sm-6 col-md-4 col-lg-3 ">
<flight-card [item]="f" [(selectedItem)]="selectedFlight"> </flight-card>
<!--
[selectedItem]="selectedFlight"
(selectedItemChange)="selectedFlight = $event"
-->
<alt-flight-card
[item]="f"
[selected]="f == selectedFlight"
(selectedChange)="selectedFlight = f"
>
</alt-flight-card>
</div>
</div>
<!--
<flight-list [flights]="flights" [(selectedFlight)]="selectedFlight">
</flight-list>
-->
<div class="row" style="margin-top:40px">
<pre>
Warenkorb
----------------------
{{ selectedFlight | json }}
</pre
>
</div>

View File

@@ -1,76 +0,0 @@
import { Component } from '@angular/core';
import { Flight } from '../../entities/flight';
import { FlightService } from '../services/flight.service';
import {
FormGroup,
FormBuilder,
Validators,
AbstractControl
} from '@angular/forms';
import { CityValidatorDirective } from '../../shared/validation/city.validator';
@Component({
selector: 'flight-search-reactive',
templateUrl: 'flight-search-reactive.component.html',
providers: [FlightService],
styleUrls: ['flight-search-reactive.component.css']
})
export class FlightSearchReactiveComponent {
public flights: Array<Flight> = [];
public selectedFlight: Flight;
public filter: FormGroup;
public formDesc = [];
constructor(private flightService: FlightService, private fb: FormBuilder) {
this.formDesc.push({
label: 'Von',
name: 'from'
});
this.formDesc.push({
label: 'Nach',
name: 'to'
});
this.filter = fb.group({
from: [
'Graz',
[
Validators.required,
Validators.minLength(3),
(c: AbstractControl): any => {
if (c.value != 'Graz' && c.value != 'Hamburg') {
return {
city: true
};
}
return {};
}
]
],
to: ['Hamburg']
});
this.filter.valueChanges.subscribe(e => {
console.debug('formular geändert', e);
});
this.filter.controls['from'].valueChanges.subscribe(e => {
console.debug('from geändert', e);
});
}
public select(f: Flight): void {
this.selectedFlight = f;
}
public search(): void {
var value = this.filter.value;
this.flightService.find(value.from, value.to);
// .map(function(resp) { return resp.json() })
}
}

View File

@@ -1,11 +0,0 @@
input.ng-valid {
border-left-style: solid;
border-left-color: forestgreen;
border-left-width: 5px;
}
input.ng-invalid {
border-left-style: solid;
border-left-color: hotpink;
border-left-width: 5px;
}

View File

@@ -1,123 +0,0 @@
<h1>Flight Search!</h1>
<!--
round-trip
-->
<form #f="ngForm" novalidate round-trip>
<div *ngIf="f?.control?.hasError('round-trip')">
Rund-Flüge sind nicht möglich.
</div>
<div class="form-group">
<label>From</label>
<input
class="form-control"
[(ngModel)]="from"
name="from"
required
minlength="3"
maxlength="30"
city="Hamburg,Wien,Graz,Frankfurt,Luxemburg,Luxembourg"
async-city
pattern="[a-zA-ZöäüßÖÄÜ]+"
/>
<div *ngIf="!f?.controls?.from?.valid && !f?.controls?.from?.pending">
Validierungsfehler. Bitte prüfen Sie Ihre Eingaben.
<pre>
{{ f.controls.from?.errors | json }}
</pre
>
</div>
<div *ngIf="f?.controls?.from?.hasError('async-city')">
Async-City: Die Stadt wird gerade wegen eines Unwetters nicht angeflogen.
</div>
<div *ngIf="f?.controls?.from?.pending">
<marquee>
Validierung wird ausgeführt. Bitte etwas warten!
</marquee>
</div>
<div *ngIf="f?.controls?.from?.hasError('required')">
Dieses Feld ist ein Pflichtfeld.
</div>
<div *ngIf="f?.controls?.from?.hasError('city')">
Diese Stadt wird nicht angeflogen.
</div>
<div *ngIf="f?.controls?.from?.hasError('minlength')">
Bitte erfassen Sie min. 3 Zeichen.
</div>
<div *ngIf="f?.controls?.from?.hasError('pattern')">
Bitte nur Buchstaben erfassen.
</div>
</div>
<div class="form-group">
<label>To</label>
<input class="form-control" [(ngModel)]="to" name="to" />
</div>
<div class="form-group">
<button
class="btn btn-primary"
name="btnSearch"
[disabled]="!f.valid"
(click)="search()"
>
Search
</button>
</div>
</form>
<!--
[ngClass]="{ 'active': f == selectedFlight }"
-->
<!--
<table class="table table-striped">
<tr *ngFor="let f of flights" [ngStyle]="{ 'background-color': (f == selectedFlight) ? 'orange': '' } ">
<td>{{f.id}}</td>
<td>{{f.from | city:'short' }}</td>
<td>{{f.to | city:'long' }}</td>
<td>{{f.date | date:'dd.MM.yyyy HH:mm'}}</td>
<td><a (click)="select(f)">Auswählen</a></td>
</tr>
</table>
-->
<div class="row">
<div *ngFor="let f of flights" class="col-sm-6 col-md-4 col-lg-3 ">
<flight-card [item]="f" [(selectedItem)]="selectedFlight"> </flight-card>
<!--
[selectedItem]="selectedFlight"
(selectedItemChange)="selectedFlight = $event"
-->
<!--
<alt-flight-card
[item]="f"
[selected]="f == selectedFlight"
(selectedChange)="selectedFlight = f">
</alt-flight-card>
-->
</div>
</div>
<!--
<flight-list [flights]="flights" [(selectedFlight)]="selectedFlight">
</flight-list>
-->
<div class="row" style="margin-top:40px">
<pre>
Warenkorb
----------------------
{{ selectedFlight | json }}
</pre
>
</div>
<button class="btn btn-primary" (click)="refresh()">Refresh Token</button>

View File

@@ -1,56 +0,0 @@
import { Component } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { Flight } from '../../entities/flight';
import { FlightService } from '../services/flight.service';
@Component({
selector: 'flight-search',
templateUrl: './flight-search.component.html',
styleUrls: ['./flight-search.component.css']
})
export class FlightSearchComponent {
public from: string = 'Graz';
public to: string = '';
public selectedFlight: Flight;
constructor(
private flightService: FlightService,
private oauthService: OAuthService
) {
console.debug('access-token', this.oauthService.getAccessToken());
}
// cmp.flights
public get flights() {
return this.flightService.flights;
}
public select(f: Flight): void {
this.selectedFlight = f;
}
public search(): void {
this.flightService.find(this.from, this.to);
// .map(function(resp) { return resp.json() })
}
refresh() {
this.oauthService.oidc = true;
if (
!this.oauthService.useSilentRefresh &&
this.oauthService.responseType === 'code'
) {
this.oauthService
.refreshToken()
.then(info => console.debug('refresh ok', info))
.catch(err => console.error('refresh error', err));
} else {
this.oauthService
.silentRefresh()
.then(info => console.debug('silent refresh ok', info))
.catch(err => console.error('silent refresh error', err));
}
}
}

View File

@@ -1,18 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
@Component({
template: `
<h1>PassengerSearch</h1>
<p>Platzhalter-Seite. Hier könnte auch Ihre Werbung stehen ;-)</p>
<p><button (click)="refresh()">Refresh</button></p>
`
})
export class PassengerSearchComponent implements OnInit {
constructor(private oauthService: OAuthService) {}
ngOnInit() {}
refresh() {
this.oauthService.silentRefresh();
}
}

View File

@@ -1,35 +0,0 @@
import { Injectable, Inject } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { API_URL } from '../../app.config';
import { Observable } from 'rxjs';
import { Flight } from '../../entities/flight';
import { OAuthService } from 'angular-oauth2-oidc';
@Injectable()
export class FlightService {
constructor(
private oauthService: OAuthService,
private http: HttpClient
) {}
public flights: Array<Flight> = [];
find(from: string, to: string): void {
let url = API_URL + '/flight';
let headers = new HttpHeaders().set('Accept', 'application/json');
//.set('Authorization', 'Bearer ' + this.oauthService.getAccessToken());
let params = new HttpParams().set('from', from).set('to', to);
this.http
.get<Flight[]>(url, { headers, params })
.subscribe(
flights => {
this.flights = flights;
},
err => {
console.warn('status', err.status);
}
);
}
}

View File

@@ -1,13 +0,0 @@
import { Component } from '@angular/core';
@Component({
template: `
<h1>Flight History</h1>
<ul>
<li>Graz - Hamburg</li>
<li>Hamburg - Frankfurt</li>
<li>Frankfurt - Graz</li>
</ul>
`
})
export class FlightHistoryComponent {}

View File

@@ -2,7 +2,6 @@ import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, retry } from 'rxjs/operators';
import { API_URL } from '../app.config';
import { ApiResponse } from '../entities/api-response';
@Injectable({
@@ -12,11 +11,11 @@ export class ApiService {
constructor(private http: HttpClient) {}
call<T>(
relativeUrl: string,
url: string,
params?: { [param: string]: string }
): Observable<ApiResponse<T>> {
return this.http
.get<ApiResponse<T>>(API_URL + relativeUrl, { params })
.get<ApiResponse<T>>(url, { params })
.pipe(
retry(3), // retry a failed request up to 3 times
catchError(this.handleError) // then handle the error

View File

@@ -8,14 +8,7 @@ import {
import { ActivatedRoute } from '@angular/router';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import {
debounceTime,
distinctUntilChanged,
startWith,
tap,
delay,
} from 'rxjs/operators';
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
import { merge, fromEvent } from 'rxjs';
import { ApiService } from '../services/api.service';
import { TravelsDataSource } from './travel-list.datasource';
@@ -28,7 +21,13 @@ import { TravelsDataSource } from './travel-list.datasource';
export class TravelListComponent implements OnInit, AfterViewInit {
dataSource: TravelsDataSource;
displayedColumns = ['driver', 'departureDate', 'origin', 'destination', 'description'];
displayedColumns = [
'driver',
'departureDate',
'origin',
'destination',
'description',
];
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
@@ -36,10 +35,7 @@ export class TravelListComponent implements OnInit, AfterViewInit {
@ViewChild('input', { static: true }) input: ElementRef;
constructor(
private route: ActivatedRoute,
private apiService: ApiService
) {}
constructor(private route: ActivatedRoute, private apiService: ApiService) {}
ngOnInit(): void {
this.dataSource = new TravelsDataSource(this.apiService);
@@ -69,9 +65,10 @@ export class TravelListComponent implements OnInit, AfterViewInit {
loadTravels(): void {
this.dataSource.loadTravels(
this.input.nativeElement.value,
this.sort.direction,
this.sort.active,
this.sort.direction === 'asc',
this.paginator.pageIndex,
this.paginator.pageSize
this.paginator.pageSize ? this.paginator.pageSize : 3
);
}
}

View File

@@ -1,6 +1,7 @@
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import { PUBLIC_API_URL } from '../app.config';
import { ApiResponse } from '../entities/api-response';
import { Travel } from '../entities/travel';
import { TravelList } from '../entities/travel-list';
@@ -16,14 +17,21 @@ export class TravelsDataSource implements DataSource<Travel> {
loadTravels(
filter: string,
sortDirection: string,
sortColumn: string,
sortAscending: boolean,
pageIndex: number,
pageSize: number
): void {
this.loadingSubject.next(true);
this.apiService
.call<TravelList>('/travel/list', { filter })
.call<TravelList>(PUBLIC_API_URL + '/list', {
filter,
sortColumn,
sortAscending: String(sortAscending),
pageIndex: String(pageIndex),
pageSize: String(pageSize),
})
.pipe(
catchError(() => of([])),
finalize(() => this.loadingSubject.next(false))