#1162 Check for ChangeDetectorRef Before Running Change Detection

[Product Details Component]
This commit is contained in:
Sebastian
2020-10-21 10:16:50 +02:00
parent 982fd1066b
commit b1884e4c05

View File

@@ -1,18 +1,43 @@
import { ActivatedRoute, Params } from '@angular/router';
import { Component, OnInit, ViewChild, OnDestroy, ChangeDetectorRef, ElementRef, HostListener } from '@angular/core';
import {
Component,
OnInit,
ViewChild,
OnDestroy,
ChangeDetectorRef,
ElementRef,
HostListener,
ViewRef,
} from '@angular/core';
import { ProductService } from '../../../../core/services/product.service';
import { ItemDTO, ReviewDTO } from '@swagger/cat';
import { Observable, of, Subject, forkJoin, combineLatest } from 'rxjs';
import { Store } from '@ngxs/store';
import { PhotoGalleryComponent, ButtonComponent } from '@libs/ui';
import { map, delay, takeUntil, switchMap, catchError, tap, filter, take } from 'rxjs/operators';
import {
map,
delay,
takeUntil,
switchMap,
catchError,
tap,
filter,
take,
} from 'rxjs/operators';
import { ProductReview } from '../../../../core/models/product-review.model';
import { ProductDisplay } from '../../../../core/models/product-display.model';
import { AddBreadcrumb, UpdateCurrentBreadcrumbName } from '../../../../core/store/actions/breadcrumb.actions';
import {
AddBreadcrumb,
UpdateCurrentBreadcrumbName,
} from '../../../../core/store/actions/breadcrumb.actions';
import { ProductCheckoutComponent } from '../../components/product-checkout/product-checkout.component';
import { ProductReviewComponent } from '../../components/product-review/product-review.component';
import { OtherFormats } from '../../../../core/models/other-formats.model';
import { shrinkTitleAnimation, shrinkSecondaryAnimation, shrinkMainCard } from '../product-details/shrink.animation';
import {
shrinkTitleAnimation,
shrinkSecondaryAnimation,
shrinkMainCard,
} from '../product-details/shrink.animation';
import { ProductAvailabilityService } from '../../../../core/services/product-availability.service';
import { AvailabilityDTO } from '@swagger/availability';
import { BranchSelectors } from '../../../../core/store/selectors/branch.selector';
@@ -45,16 +70,23 @@ import { allowedAvailabilityStatusCodes } from 'apps/sales/src/app/core/utils/pr
export class ProductDetailsComponent implements OnInit, OnDestroy {
@ViewChild('productDetailContainer', { read: ElementRef, static: false })
private productDetailContainer: ElementRef<any>;
@ViewChild('checkout', { static: false }) checkoutDialog: ProductCheckoutComponent;
@ViewChild('photoGallery', { static: false }) photoGallery: PhotoGalleryComponent;
@ViewChild('productReview', { static: false }) productReview: ProductReviewComponent;
@ViewChild('checkout', { static: false })
checkoutDialog: ProductCheckoutComponent;
@ViewChild('photoGallery', { static: false })
photoGallery: PhotoGalleryComponent;
@ViewChild('productReview', { static: false })
productReview: ProductReviewComponent;
@ViewChild('branchesAvailabilityInfo', { static: false })
branchesAvailabilityInfo: BranchesAvalabilityOverviewComponent;
@ViewChild('printModal', { static: false }) printModal: PrinterSelectionComponent;
@ViewChild('printModal', { static: false })
printModal: PrinterSelectionComponent;
@ViewChild('addtocart', { static: false }) addToCartBtn: ButtonComponent;
@ViewChild('recommendations', { static: false }) recommendations: RecommendationsComponent;
@ViewChild('otherformats', { static: false }) elOtherformats: ProductOtherFormatsComponent;
@ViewChild('panformatsel', { read: ElementRef, static: false }) public formats: ElementRef<any>;
@ViewChild('recommendations', { static: false })
recommendations: RecommendationsComponent;
@ViewChild('otherformats', { static: false })
elOtherformats: ProductOtherFormatsComponent;
@ViewChild('panformatsel', { read: ElementRef, static: false })
public formats: ElementRef<any>;
expanded = true;
id: number;
item: ItemDTO;
@@ -83,11 +115,17 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
// availabilityStatusText: string;
get availabilityStatus() {
return this.ssc.download.status || this.ssc.store.status || this.ssc.shipping.status;
return (
this.ssc.download.status ||
this.ssc.store.status ||
this.ssc.shipping.status
);
}
get availabilityStatusText() {
return this.ssc.download.text || this.ssc.store.text || this.ssc.shipping.text;
return (
this.ssc.download.text || this.ssc.store.text || this.ssc.shipping.text
);
}
currentPickUpDate = '';
@@ -117,7 +155,8 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
fullyLoaded = false;
private errorMessage = 'Verfügbarkeitsabfrage konnte nicht durchgeführt werden.';
private errorMessage =
'Verfügbarkeitsabfrage konnte nicht durchgeführt werden.';
get storeError$() {
return of(this.storeError);
@@ -154,7 +193,10 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
}
get takeNowAvailable() {
if (this.availability.filter((t) => t.type === CheckoutType.takeNow).length > 0) {
if (
this.availability.filter((t) => t.type === CheckoutType.takeNow).length >
0
) {
return of(true);
}
return of(false);
@@ -165,7 +207,8 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
this.availability.filter(
(t) =>
t.type === CheckoutType.store &&
(allowedAvailabilityStatusCodes(t.status) || (t.av && t.av.ssc === '830' && t.av.supplier === 'G'))
(allowedAvailabilityStatusCodes(t.status) ||
(t.av && t.av.ssc === '830' && t.av.supplier === 'G'))
).length > 0
) {
return of(true);
@@ -174,14 +217,23 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
}
get shippingAvailable() {
if (this.availability.filter((t) => t.type === CheckoutType.delivery && allowedAvailabilityStatusCodes(t.status)).length > 0) {
if (
this.availability.filter(
(t) =>
t.type === CheckoutType.delivery &&
allowedAvailabilityStatusCodes(t.status)
).length > 0
) {
return of(true);
}
return of(false);
}
get downloadAvailable() {
if (this.availability.filter((t) => t.type === CheckoutType.donwload).length > 0) {
if (
this.availability.filter((t) => t.type === CheckoutType.donwload).length >
0
) {
return of(true);
}
return of(false);
@@ -189,8 +241,11 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
get showAvLoading() {
if (
((!this.storeAvLoaded || !this.shippingAvLoaded) && this.product.formatIcon !== 'EB' && this.product.formatIcon !== 'DL') ||
(!this.downloadLoaded && (this.product.formatIcon === 'EB' || this.product.formatIcon === 'DL'))
((!this.storeAvLoaded || !this.shippingAvLoaded) &&
this.product.formatIcon !== 'EB' &&
this.product.formatIcon !== 'DL') ||
(!this.downloadLoaded &&
(this.product.formatIcon === 'EB' || this.product.formatIcon === 'DL'))
) {
return of(true);
}
@@ -198,7 +253,10 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
}
get isDownload() {
if (this.product && (this.product.formatIcon === 'EB' || this.product.formatIcon === 'DL')) {
if (
this.product &&
(this.product.formatIcon === 'EB' || this.product.formatIcon === 'DL')
) {
return of(true);
}
return of(false);
@@ -206,11 +264,21 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
get productAvailable() {
const availabilityLoaded =
this.availability.length > 0 && ((this.storeAvLoaded === true && this.shippingAvLoaded === true) || this.downloadLoaded === true);
const hasTakeNow = this.availability.find((t) => t.type === CheckoutType.takeNow) !== undefined;
const hasStore = this.availability.find((t) => t.type === CheckoutType.store) !== undefined;
const hasShipping = this.availability.find((t) => t.type === CheckoutType.delivery) !== undefined;
const hasDownload = this.availability.find((t) => t.type === CheckoutType.donwload) !== undefined;
this.availability.length > 0 &&
((this.storeAvLoaded === true && this.shippingAvLoaded === true) ||
this.downloadLoaded === true);
const hasTakeNow =
this.availability.find((t) => t.type === CheckoutType.takeNow) !==
undefined;
const hasStore =
this.availability.find((t) => t.type === CheckoutType.store) !==
undefined;
const hasShipping =
this.availability.find((t) => t.type === CheckoutType.delivery) !==
undefined;
const hasDownload =
this.availability.find((t) => t.type === CheckoutType.donwload) !==
undefined;
const canBeBought = hasTakeNow || hasStore || hasShipping || hasDownload;
if (canBeBought && availabilityLoaded) {
return true;
@@ -234,9 +302,17 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
if (this.product && !this.product.price) {
return true;
}
const isDownload = this.product && (this.product.formatIcon === 'EB' || this.product.formatIcon === 'DL');
const isDownloadAlreadyAdded = isDownload && this.downloadIds.includes(this.item.id);
if (this.cartHasItems && this.cartHasDownload && isDownload && isDownloadAlreadyAdded) {
const isDownload =
this.product &&
(this.product.formatIcon === 'EB' || this.product.formatIcon === 'DL');
const isDownloadAlreadyAdded =
isDownload && this.downloadIds.includes(this.item.id);
if (
this.cartHasItems &&
this.cartHasDownload &&
isDownload &&
isDownloadAlreadyAdded
) {
return true;
}
if (isDownload && this.downloadError) {
@@ -245,8 +321,12 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
if (
isDownload &&
this.downloadLoaded &&
this.availability.filter((t) => t.type === CheckoutType.donwload && t.quantity > 0 && allowedAvailabilityStatusCodes(t.status))
.length < 1
this.availability.filter(
(t) =>
t.type === CheckoutType.donwload &&
t.quantity > 0 &&
allowedAvailabilityStatusCodes(t.status)
).length < 1
) {
return true;
}
@@ -255,8 +335,12 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
!isDownload &&
this.storeAvLoaded &&
this.shippingAvLoaded &&
this.availability.filter((t) => t.type !== CheckoutType.donwload && t.quantity > 0 && allowedAvailabilityStatusCodes(t.status))
.length < 1
this.availability.filter(
(t) =>
t.type !== CheckoutType.donwload &&
t.quantity > 0 &&
allowedAvailabilityStatusCodes(t.status)
).length < 1
) {
return true;
}
@@ -349,14 +433,22 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
this.showFeatures = true;
}
if (!this.product.fullDescription || (this.product.fullDescription && this.product.fullDescription.length < 1)) {
if (
!this.product.fullDescription ||
(this.product.fullDescription &&
this.product.fullDescription.length < 1)
) {
this.showNoDescription = true;
}
this.processReviewData(item.reviews);
this.loadBranches();
if (this.product && this.product.formatIcon !== 'EB' && this.product.formatIcon !== 'DL') {
if (
this.product &&
this.product.formatIcon !== 'EB' &&
this.product.formatIcon !== 'DL'
) {
setTimeout(() => {
this.loadAvailability(item, item.product.ean);
});
@@ -375,7 +467,11 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
}
private loadTempData() {
combineLatest([this.store.select(SharedSelectors.getProcessSelectedItem), this.route.params, this.route.queryParams])
combineLatest([
this.store.select(SharedSelectors.getProcessSelectedItem),
this.route.params,
this.route.queryParams,
])
.pipe(takeUntil(this.destroy$), this.filterTempData)
.subscribe((item) => {
if (this.productDetailContainer) {
@@ -384,7 +480,10 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
this.item = item;
this.product = this.productDetailMapper(item);
this.product.fullDescription =
this.product.fullDescription && this.product.fullDescription.length > 0 ? this.product.fullDescription : ' ';
this.product.fullDescription &&
this.product.fullDescription.length > 0
? this.product.fullDescription
: ' ';
if (this.product.features.length > 0) {
this.features = this.product.features;
@@ -393,7 +492,9 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
});
}
private filterTempData = (obs: Observable<[ItemDTO, Params, Params]>): Observable<ItemDTO> => {
private filterTempData = (
obs: Observable<[ItemDTO, Params, Params]>
): Observable<ItemDTO> => {
return obs.pipe(
filter(([item, params, queryParams]) => {
if (isNullOrUndefined(item)) {
@@ -482,18 +583,20 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
}
private loadBranches() {
this.branches = this.store.selectSnapshot(BranchSelectors.getBranchesIterable);
this.branches = this.store.selectSnapshot(
BranchSelectors.getBranchesIterable
);
}
openBranchesAvailabilityModal() {
this.loadBranchesInfoComponent = true;
this.cdrf.detectChanges();
this.detectChanges();
this.branchesAvailabilityInfo.openDialog();
}
destroyAvailabilityModal() {
this.loadBranchesInfoComponent = false;
this.cdrf.detectChanges();
this.detectChanges();
}
private productDetailMapper(item: ItemDTO): ProductDisplay {
@@ -538,15 +641,21 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
// text object mapping
if (item.texts) {
item.texts.forEach((text) => {
const label = text.label ? '<b>' + text.label + '</b>' + '\n' + '<br>' : '';
const label = text.label
? '<b>' + text.label + '</b>' + '\n' + '<br>'
: '';
const value = text.value ? text.value + '\n' + '<br>' : '';
fullDescription = fullDescription ? fullDescription + label + value : label + value;
fullDescription = fullDescription
? fullDescription + label + value
: label + value;
});
}
// specs object mapping
if (item.specs) {
genre = item.specs.find((s) => s.key === this.GENRE) ? item.specs.find((s) => s.key === this.GENRE).value : '';
genre = item.specs.find((s) => s.key === this.GENRE)
? item.specs.find((s) => s.key === this.GENRE).value
: '';
recommandedAge = item.specs.find((s) => s.key === this.RECOMMANDED_AGE)
? item.specs.find((s) => s.key === this.RECOMMANDED_AGE).value
: '';
@@ -562,7 +671,9 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
if (Array.isArray(item.stockInfos) && item.stockInfos.length > 0) {
quantity = item.stockInfos[0].inStock.toString();
if (+quantity > 0) {
const userBranch = this.store.selectSnapshot(BranchSelectors.getUserBranch);
const userBranch = this.store.selectSnapshot(
BranchSelectors.getUserBranch
);
for (let x = 0; x < +quantity; x++) {
this.availability.push({
itemId: item.id,
@@ -578,7 +689,9 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
}
if (item.shelfInfos) {
assortment = item.shelfInfos[0].assortment ? item.shelfInfos[0].assortment : item.shelfInfos[0].label;
assortment = item.shelfInfos[0].assortment
? item.shelfInfos[0].assortment
: item.shelfInfos[0].label;
}
if (item.family && item.family.length > 0) {
@@ -588,7 +701,9 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
ean: t.product.ean,
format: t.product.format,
formatDetail: t.product.formatDetail,
price: !!t.catalogAvailability ? t.catalogAvailability.price.value.value : 0,
price: !!t.catalogAvailability
? t.catalogAvailability.price.value.value
: 0,
status: !!t.catalogAvailability ? t.catalogAvailability.status : 0,
});
});
@@ -649,8 +764,12 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
}
async loadDownloadAvailability(item: ItemDTO, ean: string) {
const userBranchNumber = this.store.selectSnapshot(BranchSelectors.getUserBranch);
const branch = this.store.selectSnapshot(BranchSelectors.getBranchesIterable).find((t) => t.branchNumber === userBranchNumber);
const userBranchNumber = this.store.selectSnapshot(
BranchSelectors.getUserBranch
);
const branch = this.store
.selectSnapshot(BranchSelectors.getBranchesIterable)
.find((t) => t.branchNumber === userBranchNumber);
if (!branch) {
return true;
}
@@ -658,7 +777,10 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
.getShippingAvailabilityWithCheck(item, ean, branch.id)
.pipe(takeUntil(this.destroy$))
.subscribe((response) => {
if ((response as { error: boolean; message: string; type: CheckoutType }).error) {
if (
(response as { error: boolean; message: string; type: CheckoutType })
.error
) {
this.downloadError = true;
this.downloadLoaded = true;
if (this.addToCartBtn) {
@@ -671,13 +793,19 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
av: AvailabilityDTO[];
};
if (successfulResponse) {
const preferredAvailability = successfulResponse.av.find((t) => t.preferred === 1 && t.supplier === 'DIG');
const preferredAvailability = successfulResponse.av.find(
(t) => t.preferred === 1 && t.supplier === 'DIG'
);
if (preferredAvailability) {
this.ssc.download = {
status: preferredAvailability.ssc,
text: preferredAvailability.sscText,
};
if (preferredAvailability.price && preferredAvailability.price.value && preferredAvailability.price.value.value) {
if (
preferredAvailability.price &&
preferredAvailability.price.value &&
preferredAvailability.price.value.value
) {
this.downloadPrice = preferredAvailability.price.value.value;
}
this.availability.push({
@@ -713,10 +841,15 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
async loadAvailability(it: ItemDTO, ean: string): Promise<boolean> {
const availabilityObservables: Observable<
{ branchId: number; type: CheckoutType; av: AvailabilityDTO[] } | { error: boolean; message: string; type: CheckoutType }
| { branchId: number; type: CheckoutType; av: AvailabilityDTO[] }
| { error: boolean; message: string; type: CheckoutType }
>[] = [];
const userBranchNumber = this.store.selectSnapshot(BranchSelectors.getUserBranch);
const branch = this.store.selectSnapshot(BranchSelectors.getBranchesIterable).find((t) => t.branchNumber === userBranchNumber);
const userBranchNumber = this.store.selectSnapshot(
BranchSelectors.getUserBranch
);
const branch = this.store
.selectSnapshot(BranchSelectors.getBranchesIterable)
.find((t) => t.branchNumber === userBranchNumber);
if (!branch) {
return true;
}
@@ -775,7 +908,9 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
if (item.type === CheckoutType.delivery) {
this.deliveryError = null;
}
const preferredAvailability = this.getPreferedAvailability(item.av);
const preferredAvailability = this.getPreferedAvailability(
item.av
);
if (!preferredAvailability) {
return;
}
@@ -785,19 +920,36 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
text: preferredAvailability.sscText,
};
this.currentPickUpDate = preferredAvailability.at
? this.datePipe.transform(new Date(preferredAvailability.at), 'dd.MM.yy')
? this.datePipe.transform(
new Date(preferredAvailability.at),
'dd.MM.yy'
)
: null;
if (preferredAvailability.price && preferredAvailability.price.value && preferredAvailability.price.value.value) {
if (
preferredAvailability.price &&
preferredAvailability.price.value &&
preferredAvailability.price.value.value
) {
this.pickUpPrice = preferredAvailability.price.value.value;
}
}
if (item.type === CheckoutType.delivery && preferredAvailability.at) {
if (
item.type === CheckoutType.delivery &&
preferredAvailability.at
) {
this.currentDeliveryDate = preferredAvailability.at
? this.datePipe.transform(new Date(preferredAvailability.at), 'dd.MM.yy')
? this.datePipe.transform(
new Date(preferredAvailability.at),
'dd.MM.yy'
)
: null;
}
if (item.type === CheckoutType.delivery) {
if (preferredAvailability.price && preferredAvailability.price.value && preferredAvailability.price.value.value) {
if (
preferredAvailability.price &&
preferredAvailability.price.value &&
preferredAvailability.price.value.value
) {
this.deliveryPrice = preferredAvailability.price.value.value;
}
this.ssc.shipping = {
@@ -827,7 +979,7 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
this.addToCartBtn.stopLoading();
}
this.cdrf.detectChanges();
this.detectChanges();
});
return true;
}
@@ -844,13 +996,17 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
if (stockInfo.length === 1) {
return stockInfo[0].inStock;
}
return stockInfo ? stockInfo.map((t) => t.inStock).reduce((s1, s2) => s1 + s2) : 0;
return stockInfo
? stockInfo.map((t) => t.inStock).reduce((s1, s2) => s1 + s2)
: 0;
})
)
.subscribe((inStockAv) => {
if (inStockAv) {
this.product.quantity = inStockAv + '';
this.availability = this.availability.filter((t) => t.type !== CheckoutType.takeNow);
this.availability = this.availability.filter(
(t) => t.type !== CheckoutType.takeNow
);
this.availability.push({
itemId: itemId,
branchId: branchId,
@@ -872,7 +1028,9 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
if (!availability) {
return;
}
const preferredAvailability = availability.find((ava: AvailabilityDTO) => ava.preferred === 1);
const preferredAvailability = availability.find(
(ava: AvailabilityDTO) => ava.preferred === 1
);
return preferredAvailability; // ? preferredAvailability : availability[0];
}
@@ -920,7 +1078,12 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
expand() {
this.expanded = !this.expanded;
this.store.dispatch(new UpdateCurrentBreadcrumbName(this.expanded ? this.product.title : 'Empfehlungen', 'product'));
this.store.dispatch(
new UpdateCurrentBreadcrumbName(
this.expanded ? this.product.title : 'Empfehlungen',
'product'
)
);
if (!this.expanded) {
this.recommendations.loadReccomendations();
}
@@ -949,9 +1112,24 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
if (event.target.scrollTop === 0) {
this.elOtherformats.calculateTopPosition();
}
if (event.target.scrollTop === event.target.scrollHeight - event.target.offsetHeight) {
if (
event.target.scrollTop ===
event.target.scrollHeight - event.target.offsetHeight
) {
this.elOtherformats.calculateTopPosition();
}
}
}
detectChanges() {
setTimeout(() => {
if (
this.cdrf !== null &&
this.cdrf !== undefined &&
!(this.cdrf as ViewRef).destroyed
) {
this.cdrf.detectChanges();
}
}, 0);
}
}