diff --git a/.changeset/fair-colts-explain.md b/.changeset/fair-colts-explain.md new file mode 100644 index 00000000..0ffdc2dc --- /dev/null +++ b/.changeset/fair-colts-explain.md @@ -0,0 +1,5 @@ +--- +'@openstapps/app': patch +--- + +Use observable chains instead of change detection in the rating component diff --git a/frontend/app/src/app/modules/data/elements/rating.component.ts b/frontend/app/src/app/modules/data/elements/rating.component.ts index 03f423cb..75eabf98 100644 --- a/frontend/app/src/app/modules/data/elements/rating.component.ts +++ b/frontend/app/src/app/modules/data/elements/rating.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable unicorn/no-useless-undefined */ /* * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it @@ -12,60 +13,51 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -import {Component, ElementRef, HostListener, Input} from '@angular/core'; -import {SCDish, SCRatingRequest, SCUuid} from '@openstapps/core'; +import {ChangeDetectionStrategy, Component, ElementRef, HostListener, Input} from '@angular/core'; +import {SCDish, SCRatingRequest} from '@openstapps/core'; import {RatingProvider} from '../rating.provider'; import {ratingAnimation} from './rating.animation'; +import {BehaviorSubject, filter, merge, mergeMap, of, ReplaySubject, withLatestFrom} from 'rxjs'; +import {catchError, map} from 'rxjs/operators'; @Component({ selector: 'stapps-rating', templateUrl: 'rating.html', styleUrls: ['rating.scss'], animations: [ratingAnimation], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class StappsRatingComponent { - rate = false; + performRating = new BehaviorSubject(false); - rated = false; + userRating = new BehaviorSubject(undefined); - canBeRated = false; + dish = new ReplaySubject(1); - uid: SCUuid; + wasAlreadyRated = merge( + this.dish.pipe(mergeMap(({uid}) => this.ratingProvider.hasRated(uid))), + this.userRating.pipe( + filter(it => it !== undefined), + withLatestFrom(this.dish), + mergeMap(([rating, {uid}]) => this.ratingProvider.rate(uid, rating as SCRatingRequest['rating'])), + map(() => true), + catchError(() => of(false)), + ), + ); - rating?: number; + canBeRated = this.dish.pipe(mergeMap(dish => this.ratingProvider.canRate(dish))); - @Input() set item(value: SCDish) { - this.uid = value.uid; - - Promise.all([this.ratingProvider.canRate(value), this.ratingProvider.hasRated(this.uid)] as const).then( - ([canRate, hasRated]) => { - this.canBeRated = canRate; - this.rated = hasRated; - }, - ); + @Input({required: true}) set item(value: SCDish) { + this.dish.next(value); } constructor(readonly elementRef: ElementRef, readonly ratingProvider: RatingProvider) {} - async submitRating(rating: number) { - this.rating = rating; - try { - await this.ratingProvider.rate(this.uid, rating as SCRatingRequest['rating']); - this.rated = true; - } catch { - this.rating = undefined; - // allow change detection to catch up first - setTimeout(() => { - this.rate = false; - }); - } - } - @HostListener('document:mousedown', ['$event']) clickOutside(event: MouseEvent) { - if (this.rating) return; + if (this.userRating.value) return; if (!this.elementRef.nativeElement.contains(event.target)) { - this.rate = false; + this.performRating.next(false); } } } diff --git a/frontend/app/src/app/modules/data/elements/rating.html b/frontend/app/src/app/modules/data/elements/rating.html index 2248eb4b..05cec835 100644 --- a/frontend/app/src/app/modules/data/elements/rating.html +++ b/frontend/app/src/app/modules/data/elements/rating.html @@ -14,19 +14,23 @@ --> -
+