#1460 Implemented Scroll Position Service with Prefetch

This commit is contained in:
Nino Righi
2021-03-04 16:35:06 +01:00
committed by Lorenz Hilpert
parent cd0dcca911
commit fd60dca5ab
8 changed files with 100 additions and 75 deletions

View File

@@ -7,6 +7,7 @@ import { BreadcrumbService } from '@core/breadcrumb';
import { CrmCustomerService } from '@domain/crm';
import { CountryDTO, CustomerDTO, KeyValueDTOOfStringAndString } from '@swagger/crm';
import { UiValidators } from '@ui/common';
import { ScrollPositionService } from 'apps/ui/common/src/lib/scroll-position/scroll-position.service';
import { Observable } from 'rxjs';
import { first, map, switchMap } from 'rxjs/operators';
import { validateEmail } from '../../validators/email-validator';
@@ -34,7 +35,8 @@ export abstract class CustomerDataEditComponent implements OnInit {
private cdr: ChangeDetectorRef,
private location: Location,
private breadcrumb: BreadcrumbService,
private application: ApplicationService
private application: ApplicationService,
private scrollPositionService: ScrollPositionService
) {}
ngOnInit() {
@@ -126,6 +128,12 @@ export abstract class CustomerDataEditComponent implements OnInit {
try {
await this.customerService.patchCustomer(this.customerId, this.control.value).toPromise();
if (!!this.scrollPositionService.data) {
const index = this.scrollPositionService.data?.result?.findIndex((customer) => customer.id === this.customerId);
const obj = this.scrollPositionService.data?.result[index];
const updatedObj = Object.assign(obj, this.control.value);
this.scrollPositionService.data.result.splice(index, 1, updatedObj);
}
this.location.back();
} catch (err) {
this.control.enable();

View File

@@ -1,5 +1,5 @@
import { Component, ChangeDetectionStrategy, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core';
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
import { Component, ChangeDetectionStrategy, OnInit, ChangeDetectorRef } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService, ProcessService } from '@core/application';
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
import { CartService } from '@domain/cart';
@@ -8,7 +8,7 @@ import { AddressHelper, AssignedPayerHelper, CrmCustomerService } from '@domain/
import { CustomerDTO, KeyValueDTOOfStringAndString } from '@swagger/crm';
import { UiModalService } from '@ui/modal';
import { ShippingAddressHelper } from 'apps/domain/crm/src/lib/helpers/shipping-address.helper';
import { Observable, Subscription } from 'rxjs';
import { Observable } from 'rxjs';
import { first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { CantAddCustomerToCartModalComponent } from '../modals/cant-add-customer-to-cart-modal/cant-add-customer-to-cart.component';
import { CantAddCustomerToCartData } from '../modals/cant-add-customer-to-cart-modal/cant-add-customer-to-cart.data';
@@ -19,7 +19,7 @@ import { CantAddCustomerToCartData } from '../modals/cant-add-customer-to-cart-m
styleUrls: ['customer-details.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerDetailsComponent implements OnInit, OnDestroy {
export class CustomerDetailsComponent implements OnInit {
customerId$: Observable<number>;
customer$: Observable<CustomerDTO>;
@@ -39,8 +39,6 @@ export class CustomerDetailsComponent implements OnInit, OnDestroy {
private currentBreadcrumb: Breadcrumb;
scrollSubscription: Subscription;
constructor(
private activatedRoute: ActivatedRoute,
private customerDetailsService: CrmCustomerService,
@@ -106,15 +104,6 @@ export class CustomerDetailsComponent implements OnInit, OnDestroy {
)
);
this.scrollSubscription = this.router.events.subscribe((events) => {
if (events instanceof NavigationStart) {
if (!events?.url.includes('/customer/search') && !events?.url.includes('query')) {
// Scrollposition im sessionStorage löschen wenn nicht zur suche zurück navigiert wird
sessionStorage.removeItem('scrollPos');
}
}
});
this.isB2b$ = this.customerType$.pipe(map((type) => type === 'b2b'));
this.isWebshopOrGuest$ = this.customerType$.pipe(map((type) => type === 'webshop' || type === 'guest'));
@@ -124,12 +113,6 @@ export class CustomerDetailsComponent implements OnInit, OnDestroy {
this.createBreadcrumb();
}
ngOnDestroy() {
if (!!this.scrollSubscription) {
this.scrollSubscription.unsubscribe();
}
}
async createBreadcrumb() {
const customerId = await this.customerId$.pipe(first()).toPromise();

View File

@@ -10,6 +10,7 @@ import { NativeContainerService } from 'native-container';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { delay, map, switchMap } from 'rxjs/operators';
import { CustomerSearch } from './customer-search.service';
import { ScrollPositionService } from 'apps/ui/common/src/lib/scroll-position/scroll-position.service';
@Component({
selector: 'page-customer-search',
@@ -37,9 +38,10 @@ export class CustomerSearchComponent extends CustomerSearch implements OnInit {
filterMapping: UiFilterMappingService,
nativeContainer: NativeContainerService,
application: ApplicationService,
breadcrumb: BreadcrumbService
breadcrumb: BreadcrumbService,
scrollPositionService: ScrollPositionService
) {
super(zone, router, application, breadcrumb, env, filterMapping, nativeContainer);
super(zone, router, application, breadcrumb, env, filterMapping, nativeContainer, scrollPositionService);
}
ngOnInit() {

View File

@@ -13,6 +13,7 @@ import { NativeContainerService } from 'native-container';
import { StringDictionary } from '@cmf/core';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { ScrollPositionService } from 'apps/ui/common/src/lib/scroll-position/scroll-position.service';
@Injectable()
export abstract class CustomerSearch implements OnInit, OnDestroy {
@@ -47,8 +48,6 @@ export abstract class CustomerSearch implements OnInit, OnDestroy {
result: [],
});
searchCompleted = new Subject();
public searchState$ = new BehaviorSubject<ResultState>('init');
get searchState(): ResultState {
@@ -82,7 +81,8 @@ export abstract class CustomerSearch implements OnInit, OnDestroy {
private breadcrumb: BreadcrumbService,
private environmentService: EnvironmentService,
private filterMapping: UiFilterMappingService,
private nativeContainer: NativeContainerService
private nativeContainer: NativeContainerService,
private scrollPositionService: ScrollPositionService
) {}
ngOnInit() {
@@ -253,7 +253,11 @@ export abstract class CustomerSearch implements OnInit, OnDestroy {
switchMap(([effectiveTake, hitsTake]) => {
return this.customerSearch
.getCustomers(this.queryFilter.query, {
skip: options.isNewSearch ? 0 : this.numberOfResultsFetched - effectiveTake,
skip: options.isNewSearch
? 0
: !!this.scrollPositionService.data
? this.numberOfResultsFetched + this.scrollPositionService.data?.skip
: this.numberOfResultsFetched - effectiveTake,
take: hitsTake,
filter: this.getSelecteFiltersAsDictionary(),
})
@@ -297,18 +301,29 @@ export abstract class CustomerSearch implements OnInit, OnDestroy {
}
}
} else {
// TODO: remove that and implement inside search results component
// if (!!this.scrollPositionService.data) {
// this.searchResult$.next({
// ...r,
// hits,
// result: [
// ...this.scrollPositionService.data.result,
// ...this.searchResult$.value.result,
// ...r.result.map((a) => ({ ...a, loaded: true })),
// ],
// });
// } else {
this.searchResult$.next({
...r,
hits,
result: [...this.searchResult$.value.result, ...r.result.map((a) => ({ ...a, loaded: true }))],
});
// }
}
if (hits > 0) {
this.filterActive$.next(false);
}
this.searchCompleted.next();
});
}
}

View File

@@ -1,8 +1,8 @@
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { EnvironmentService } from '@core/environment';
import { ScrollPositionService } from 'apps/ui/common/src/lib/scroll-position/scroll-position.service';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { CustomerSearch } from '../customer-search.service';
@@ -23,13 +23,14 @@ export class CustomerSearchMainComponent implements OnInit, OnDestroy {
public cdr: ChangeDetectorRef,
public environmentService: EnvironmentService,
public application: ApplicationService,
private breadcrumb: BreadcrumbService
private breadcrumb: BreadcrumbService,
private scrollPositionService: ScrollPositionService
) {}
ngOnInit() {
this.detectDevice();
this.initBreadcrumb();
sessionStorage.removeItem('scrollPos');
this.scrollPositionService.resetService();
}
initBreadcrumb() {

View File

@@ -2,8 +2,9 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, OnDestro
import { NavigationStart, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
import { ScrollPositionService } from 'apps/ui/common/src/lib/scroll-position/scroll-position.service';
import { Observable, Subscription } from 'rxjs';
import { filter, map, take } from 'rxjs/operators';
import { filter, first, map, take } from 'rxjs/operators';
import { CustomerSearch } from '../customer-search.service';
import { CustomerSearchType } from '../defs';
@@ -18,7 +19,6 @@ export class CustomerSearchResultComponent implements OnInit, OnDestroy, AfterVi
private _breadcrumb: Breadcrumb;
customers$: Observable<CustomerSearchType[]>;
scrollSubscription: Subscription;
onLoadSubscription: Subscription;
protected readonly viewportEnterOptions: IntersectionObserverInit = {
threshold: 0.75,
@@ -28,36 +28,35 @@ export class CustomerSearchResultComponent implements OnInit, OnDestroy, AfterVi
private application: ApplicationService,
private breadcrumb: BreadcrumbService,
public search: CustomerSearch,
private router: Router
private router: Router,
private scrollPositionService: ScrollPositionService
) {}
ngOnInit() {
this.customers$ = this.search.searchResult$.pipe(
map((response) => {
// console.log(response);
if (!!this.scrollPositionService.data) {
return [...this.scrollPositionService.data.result, ...response.result];
}
return response.result;
})
);
// Safe current scroll position if navigating to Customer Details
this.scrollSubscription = this.router.events.subscribe((events) => {
if (events instanceof NavigationStart) {
const customerId = Number.parseInt(events.url.replace(/\D/g, ''), 10);
if (events?.url === `/customer/${customerId}`) {
// Safe Scrollposition inside sessionStorage
sessionStorage.setItem('scrollPos', `${this.scrollContainer.nativeElement.scrollTop}`);
// - TESTING -
// Safe Loaded customers
// --> SAFE EDITED DATA TO LOADED CUSTOMERS ASWELL !!
// this.search.searchResult$.subscribe((res) => {
// console.log(res.result);
// sessionStorage.setItem('preloadedCustomers', JSON.stringify(res.result));
// });
console.log(this.search.searchResult$.value.result);
// sessionStorage.setItem('preloadedCustomers', JSON.stringify(this.search.searchResult$.value.result));
} else {
sessionStorage.removeItem('scrollPos');
// sessionStorage.removeItem('preloadedCustomers');
// Safe Scrollposition
this.scrollPositionService.scrollPosition = this.scrollContainer.nativeElement.scrollTop;
// Safe prefetched data
if (!!this.scrollPositionService.data) {
const oldResult = this.scrollPositionService.data.result;
const newResult = this.search.searchResult$.value.result;
this.scrollPositionService.data = this.search.searchResult$.value;
this.scrollPositionService.data.result = [...oldResult, ...newResult];
} else {
this.scrollPositionService.data = this.search.searchResult$.value;
}
}
}
});
@@ -66,25 +65,7 @@ export class CustomerSearchResultComponent implements OnInit, OnDestroy, AfterVi
ngAfterViewInit() {
// Start scrolling to last remembered position
this.scrollContainer.nativeElement.scrollTo(0, Number(sessionStorage.getItem('scrollPos')));
// ### SHOULD BE REMOVED IF CONTENT GETS PRELOADED ###
// If content needs to be loaded, invoke the scroll again
this.onLoadSubscription = this.search.searchCompleted.subscribe(() => {
const container = this.scrollContainer.nativeElement.scrollTop;
const storage = Number(sessionStorage.getItem('scrollPos'));
if (container <= storage) {
// Scroll to content
this.scrollContainer.nativeElement.scrollTo(0, storage);
} else if (storage <= 1100) {
// Stop scrolling if needed content is loaded without loading more items
this.onLoadSubscription.unsubscribe();
} else {
// Jump to content after items loaded and stop scrolling
this.scrollContainer.nativeElement.scrollTo(0, storage);
this.onLoadSubscription.unsubscribe();
}
});
this.scrollPositionService.scrollToLastRememberedPosition(this.scrollContainer);
}
async initBreadcrumb() {
@@ -118,13 +99,9 @@ export class CustomerSearchResultComponent implements OnInit, OnDestroy, AfterVi
}
ngOnDestroy() {
this.search.searchState$.next('init');
if (!!this.scrollSubscription) {
this.scrollSubscription.unsubscribe();
}
if (!!this.onLoadSubscription) {
this.onLoadSubscription.unsubscribe();
}
}
checkIfReload(target: HTMLElement): void {

View File

@@ -1,10 +1,12 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { ApplicationService } from '@core/application';
import { ScrollPositionService } from 'apps/ui/common/src/lib/scroll-position/scroll-position.service';
@Component({
selector: 'page-customer',
templateUrl: 'page-customer.component.html',
styleUrls: ['page-customer.component.scss'],
providers: [ScrollPositionService],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PageCustomerComponent {

View File

@@ -0,0 +1,37 @@
import { ElementRef, Injectable } from '@angular/core';
@Injectable()
export class ScrollPositionService {
private scrollPos = 0;
private prefetchedData: any;
get scrollPosition(): number {
return this.scrollPos;
}
set scrollPosition(scrollPosition: number) {
this.scrollPos = scrollPosition;
}
get data(): any {
return this.prefetchedData;
}
set data(data: any) {
this.prefetchedData = data;
}
constructor() {}
resetService() {
this.scrollPosition = 0;
this.data = undefined;
}
scrollToLastRememberedPosition(elementRef: ElementRef<any>) {
// Skip initial call
if (!!this.scrollPosition && !!this.data) {
elementRef.nativeElement.scrollTo(0, this.scrollPosition);
}
}
}