Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/marp 1118 reloading of webpage caused a flickering #254

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0850286
Update UI
ntqdinh-axonivy Dec 10, 2024
5803f59
refactor css
ntqdinh-axonivy Dec 10, 2024
d5ede2b
update html
ntqdinh-axonivy Dec 10, 2024
e71683f
update service
ntqdinh-axonivy Dec 11, 2024
73fc859
so tired
ntqdinh-axonivy Dec 11, 2024
37e57b5
update service
ntqdinh-axonivy Dec 11, 2024
e285683
updatre service
ntqdinh-axonivy Dec 11, 2024
5ac316e
update css
ntqdinh-axonivy Dec 11, 2024
0e25945
update css style
ntqdinh-axonivy Dec 11, 2024
b8fb576
update service
ntqdinh-axonivy Dec 12, 2024
9558ba7
update service
ntqdinh-axonivy Dec 12, 2024
6639723
update service
ntqdinh-axonivy Dec 12, 2024
ab6c7af
handle feedback
ntqdinh-axonivy Dec 12, 2024
f48e9a8
Merge branch 'develop' into bugfix/MARP-1118-Reloading-of-webpage-cau…
ntqdinh-axonivy Dec 13, 2024
305a97a
udpate service
ntqdinh-axonivy Dec 13, 2024
04c6628
finalize service
ntqdinh-axonivy Dec 16, 2024
afdd135
update test case
ntqdinh-axonivy Dec 16, 2024
9c03e41
update test case
ntqdinh-axonivy Dec 17, 2024
ec32a56
format code
ntqdinh-axonivy Dec 17, 2024
c523f9e
update code
ntqdinh-axonivy Dec 17, 2024
d5985a9
update code
ntqdinh-axonivy Dec 17, 2024
69457a3
Merge branch 'develop' into bugfix/MARP-1118-Reloading-of-webpage-cau…
ntqdinh-axonivy Dec 17, 2024
9b9c237
handle sonnar
ntqdinh-axonivy Dec 17, 2024
76fa3b5
update type
ntqdinh-axonivy Dec 17, 2024
8e76857
handle sonnar
ntqdinh-axonivy Dec 17, 2024
22717c6
update code
ntqdinh-axonivy Dec 17, 2024
1b5e37a
handle feedback
ntqdinh-axonivy Dec 18, 2024
d6755f6
handle feedback
ntqdinh-axonivy Dec 19, 2024
d77d457
Merge branch 'develop' into bugfix/MARP-1118-Reloading-of-webpage-cau…
ntqdinh-axonivy Dec 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions marketplace-ui/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,4 @@
<app-footer></app-footer>
</footer>
}

@if (loadingService.isLoading()) {
<app-loading-spinner />
}
</div>
5 changes: 1 addition & 4 deletions marketplace-ui/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { FooterComponent } from './shared/components/footer/footer.component';
import { HeaderComponent } from './shared/components/header/header.component';
import { LoadingService } from './core/services/loading/loading.service';
import { RoutingQueryParamService } from './shared/services/routing.query.param.service';
import { CommonModule } from '@angular/common';
import { ERROR_PAGE_PATH } from './shared/constants/common.constant';
Expand All @@ -12,18 +11,16 @@ import {
RouterOutlet,
Event
} from '@angular/router';
import { LoadingSpinnerComponent } from "./shared/components/loading-spinner/loading-spinner.component";
import { BackToTopComponent } from "./shared/components/back-to-top/back-to-top.component";

@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, HeaderComponent, FooterComponent, CommonModule, LoadingSpinnerComponent, BackToTopComponent],
imports: [RouterOutlet, HeaderComponent, FooterComponent, CommonModule, BackToTopComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
loadingService = inject(LoadingService);
routingQueryParamService = inject(RoutingQueryParamService);
route = inject(ActivatedRoute);
isMobileMenuCollapsed = true;
Expand Down
18 changes: 8 additions & 10 deletions marketplace-ui/src/app/core/interceptors/api.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ import { ERROR_CODES, ERROR_PAGE_PATH } from '../../shared/constants/common.cons
export const REQUEST_BY = 'X-Requested-By';
export const IVY = 'marketplace-website';

/** SkipLoading: This option for exclude loading api
* @Example return httpClient.get('apiEndPoint', { context: new HttpContext().set(SkipLoading, true) })
*/
export const SkipLoading = new HttpContextToken<boolean>(() => false);

/** ForwardingError: This option for forwarding responce error to the caller
* @Example return httpClient.get('apiEndPoint', { context: new HttpContext().set(ForwardingError, true) })
*/
export const ForwardingError = new HttpContextToken<boolean>(() => false);

/** LoadingComponentId: This option for show loading for component which match with id
* @Example return httpClient.get('apiEndPoint', { context: new HttpContext().set(LoadingComponentId, "detail-page") })
*/
export const LoadingComponent = new HttpContextToken<string>(() => '');

export const apiInterceptor: HttpInterceptorFn = (req, next) => {
const router = inject(Router);

const loadingService = inject(LoadingService);

if (req.url.includes('i18n')) {
Expand All @@ -41,9 +42,6 @@ export const apiInterceptor: HttpInterceptorFn = (req, next) => {
headers: addIvyHeaders(req.headers)
});

if (!req.context.get(SkipLoading)) {
loadingService.show();
}

if (req.context.get(ForwardingError)) {
return next(cloneReq);
Expand All @@ -59,8 +57,8 @@ export const apiInterceptor: HttpInterceptorFn = (req, next) => {
return EMPTY;
}),
finalize(() => {
if (!req.context.get(SkipLoading)) {
loadingService.hide();
if (req.context.get(LoadingComponent)) {
loadingService.hideLoading(req.context.get(LoadingComponent));
}
})
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { TestBed } from '@angular/core/testing';

import { LoadingService } from './loading.service';
import { LoadingComponentId } from '../../../shared/enums/loading-component-id';

describe('LoadingService', () => {
let service: LoadingService;
Expand All @@ -15,12 +16,12 @@ describe('LoadingService', () => {
});

it('show should update isLoading to true', () => {
service.show();
expect(service.isLoading()).toBeTrue();
service.showLoading(LoadingComponentId.DETAIL_PAGE);
expect(service.loadingStates()[LoadingComponentId.DETAIL_PAGE]).toBeTrue();
})

it('hide should update isLoading to false', () => {
service.hide();
expect(service.isLoading()).toBeFalse();
service.hideLoading(LoadingComponentId.DETAIL_PAGE);
expect(service.loadingStates()[LoadingComponentId.DETAIL_PAGE]).toBeFalse();
})
});
21 changes: 14 additions & 7 deletions marketplace-ui/src/app/core/services/loading/loading.service.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { computed, Injectable, signal } from '@angular/core';
import { Injectable, signal } from '@angular/core';

@Injectable({
providedIn: 'root'
})
export class LoadingService {
private readonly isShow = signal(false);
isLoading = computed(() => this.isShow());
loadingStates = signal<{ [key: string]: boolean }>({});

show() {
this.isShow.set(true);
private setLoading(componentId: string, isLoading: boolean): void {
this.loadingStates.update(states => {
const updatedStates = { ...states };
updatedStates[componentId] = isLoading;
return updatedStates;
});
}

hide() {
this.isShow.set(false);
showLoading(componentId: string): void {
this.setLoading(componentId, true);
}

hideLoading(componentId: string) {
this.setLoading(componentId, false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ describe('ProductDetailFeedbackComponent', () => {
mockProductFeedbackService = jasmine.createSpyObj(
'ProductFeedbackService',
[
'initFeedbacks',
'fetchFeedbacks',
'findProductFeedbackOfUser',
'loadMoreFeedbacks',
'areAllFeedbacksLoaded',
'totalElements'
],
{feedbacks: signal([] as Feedback[]), sort: signal('updatedAt,desc')}
{ feedbacks: signal([] as Feedback[]), sort: signal('updatedAt,desc') }
);
mockProductStarRatingService = jasmine.createSpyObj(
'ProductStarRatingService',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ describe('ProductFeedbackService', () => {

productDetailService.productId.and.returnValue('123');

service.initFeedbacks();
const req = httpMock.expectOne('api/feedback/product/123?page=0&size=8&sort=newest');
service.fetchFeedbacks();
const req = httpMock.expectOne( 'api/feedback/product/123?page=0&size=8&sort=newest' );
expect(req.request.method).toBe('GET');
req.flush(mockResponse);

Expand All @@ -85,14 +85,14 @@ describe('ProductFeedbackService', () => {
const additionalFeedback: Feedback[] = [
{ content: 'Another review', rating: 4, productId: '123' }
];

productDetailService.productId.and.returnValue('123');
service.initFeedbacks();
const initReq = httpMock.expectOne('api/feedback/product/123?page=0&size=8&sort=newest');
service.fetchFeedbacks();
const initReq = httpMock.expectOne( 'api/feedback/product/123?page=0&size=8&sort=newest' );
initReq.flush({ _embedded: { feedbacks: initialFeedback }, page: { totalPages: 2, totalElements: 5 } });

service.loadMoreFeedbacks();
const loadMoreReq = httpMock.expectOne('api/feedback/product/123?page=1&size=8&sort=newest');
const loadMoreReq = httpMock.expectOne( 'api/feedback/product/123?page=1&size=8&sort=newest' );
loadMoreReq.flush({ _embedded: { feedbacks: additionalFeedback } });

expect(service.feedbacks()).toEqual([...initialFeedback, ...additionalFeedback]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ import {
import { catchError, Observable, of, tap, throwError } from 'rxjs';
import { CookieService } from 'ngx-cookie-service';
import { AuthService } from '../../../../../auth/auth.service';
import {
ForwardingError,
SkipLoading
} from '../../../../../core/interceptors/api.interceptor';
import { ForwardingError } from '../../../../../core/interceptors/api.interceptor';
import { FeedbackApiResponse } from '../../../../../shared/models/apis/feedback-response.model';
import { Feedback } from '../../../../../shared/models/feedback.model';
import { ProductDetailService } from '../../product-detail.service';
Expand Down Expand Up @@ -64,13 +61,11 @@ export class ProductFeedbackService {
return this.http
.post<Feedback>(FEEDBACK_API_URL, feedback, {
headers,
context: new HttpContext()
.set(SkipLoading, true)
.set(ForwardingError, true)
context: new HttpContext().set(ForwardingError, true)
})
.pipe(
tap(() => {
this.initFeedbacks();
this.fetchFeedbacks();
this.findProductFeedbackOfUser().subscribe();
this.productStarRatingService.fetchData();
}),
Expand All @@ -86,7 +81,7 @@ export class ProductFeedbackService {
);
}

private findProductFeedbacksByCriteria(
findProductFeedbacksByCriteria(
productId: string = this.productDetailService.productId(),
page: number = this.page(),
sort: string = this.sort(),
Expand All @@ -100,7 +95,7 @@ export class ProductFeedbackService {
return this.http
.get<FeedbackApiResponse>(requestURL, {
params: requestParams,
context: new HttpContext().set(SkipLoading, true).set(ForwardingError, true)
context: new HttpContext().set(ForwardingError, true)
})
.pipe(
tap(response => {
Expand All @@ -126,9 +121,7 @@ export class ProductFeedbackService {
return this.http
.get<Feedback>(requestURL, {
params,
context: new HttpContext()
.set(SkipLoading, true)
.set(ForwardingError, true)
context: new HttpContext().set(ForwardingError, true)
})
.pipe(
tap(feedback => {
Expand All @@ -152,11 +145,9 @@ export class ProductFeedbackService {
);
}

initFeedbacks(): void {
this.page.set(0);
this.findProductFeedbacksByCriteria().subscribe(response => {
this.totalPages.set(response.page.totalPages);
this.totalElements.set(response.page.totalElements);
fetchFeedbacks(): void {
this.getInitFeedbacksObservable().subscribe(response => {
this.handleFeedbackApiResponse(response);
});
}

Expand All @@ -174,4 +165,14 @@ export class ProductFeedbackService {
private clearTokenCookie(): void {
this.cookieService.delete(TOKEN_KEY);
}

handleFeedbackApiResponse(response: FeedbackApiResponse): void {
this.totalPages.set(response.page.totalPages);
this.totalElements.set(response.page.totalElements);
}

getInitFeedbacksObservable(): Observable<FeedbackApiResponse> {
this.page.set(0);
return this.findProductFeedbacksByCriteria();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import {
signal,
WritableSignal
} from '@angular/core';
import { tap } from 'rxjs';
import { Observable, tap } from 'rxjs';
import { StarRatingCounting } from '../../../../../shared/models/star-rating-counting.model';
import { ProductDetailService } from '../../product-detail.service';
import { SkipLoading } from '../../../../../core/interceptors/api.interceptor';

@Injectable({
providedIn: 'root'
Expand All @@ -28,16 +27,7 @@ export class ProductStarRatingService {
);

fetchData(productId: string = this.productDetailService.productId()): void {
const requestURL = `api/feedback/product/${productId}/rating`;
this.http
.get<StarRatingCounting[]>(requestURL, {context: new HttpContext().set(SkipLoading, true)})
.pipe(
tap(data => {
this.sortByStar(data);
this.starRatings.set(data);
})
)
.subscribe();
this.getRatingObservable(productId).subscribe();
}

private sortByStar(starRatings: StarRatingCounting[]): void {
Expand All @@ -64,4 +54,16 @@ export class ProductStarRatingService {

return Math.round(reviewNumber * 10) / 10;
}

getRatingObservable(id: string): Observable<StarRatingCounting[]> {
const requestURL = `api/feedback/product/${id}/rating`;
return this.http
.get<StarRatingCounting[]>(requestURL, { context: new HttpContext() })
.pipe(
tap(data => {
this.sortByStar(data);
this.starRatings.set(data);
})
);
}
}
Loading
Loading