From 8fec8b44a091fbd9a9df93065668de14cca3b535 Mon Sep 17 00:00:00 2001 From: Abel Cota Date: Wed, 19 Aug 2026 08:59:17 -0600 Subject: [PATCH] Fix 6076: Applying changes to avoid reload screen when filters, query, search bar and other url param changes and move to the top of search component. --- .../collection-page/collection-page-routes.ts | 3 +- .../community-page/community-page-routes.ts | 3 +- src/app/core/router/utils/route.utils.ts | 40 ++++++++++++++++++- src/app/item-page/item-page-routes.ts | 3 +- .../search-form/search-form.component.spec.ts | 4 ++ .../search-form/search-form.component.ts | 6 ++- .../advanced-search.component.ts | 6 ++- .../search-facet-option.component.html | 1 + ...earch-facet-selected-option.component.html | 1 + .../search-facet-filter.component.spec.ts | 2 + .../search-facet-filter.component.ts | 6 ++- .../search-hierarchy-filter.component.spec.ts | 2 + .../search-hierarchy-filter.component.ts | 2 + .../search-range-filter.component.spec.ts | 2 + .../search-range-filter.component.ts | 2 + .../search-label-range.component.html | 2 + .../search-label/search-label.component.html | 1 + .../search-settings.component.ts | 3 ++ ...rch-switch-configuration.component.spec.ts | 2 + .../search-switch-configuration.component.ts | 6 ++- src/app/shared/search/search.component.ts | 12 +++++- 21 files changed, 100 insertions(+), 9 deletions(-) diff --git a/src/app/collection-page/collection-page-routes.ts b/src/app/collection-page/collection-page-routes.ts index 6503bbd624b..911b74eda58 100644 --- a/src/app/collection-page/collection-page-routes.ts +++ b/src/app/collection-page/collection-page-routes.ts @@ -4,6 +4,7 @@ import { collectionBreadcrumbResolver } from '@dspace/core/breadcrumbs/collectio import { communityBreadcrumbResolver } from '@dspace/core/breadcrumbs/community-breadcrumb.resolver'; import { i18nBreadcrumbResolver } from '@dspace/core/breadcrumbs/i18n-breadcrumb.resolver'; import { endUserAgreementCurrentUserGuard } from '@dspace/core/end-user-agreement/end-user-agreement-current-user.guard'; +import { rerunGuardsAndResolversOnPathChange } from '@dspace/core/router/utils/route.utils'; import { ObjectAuditLogsComponent } from '../audit-page/object-audit-overview/object-audit-logs.component'; import { browseByGuard } from '../browse-by/browse-by-guard'; @@ -57,7 +58,7 @@ export const ROUTES: Route[] = [ dso: collectionPageResolver, breadcrumb: collectionBreadcrumbResolver, }, - runGuardsAndResolvers: 'always', + runGuardsAndResolvers: rerunGuardsAndResolversOnPathChange, children: [ { path: COLLECTION_EDIT_PATH, diff --git a/src/app/community-page/community-page-routes.ts b/src/app/community-page/community-page-routes.ts index d944194ed72..d67b2dbe0c4 100644 --- a/src/app/community-page/community-page-routes.ts +++ b/src/app/community-page/community-page-routes.ts @@ -2,6 +2,7 @@ import { Route } from '@angular/router'; import { authenticatedGuard } from '@dspace/core/auth/authenticated.guard'; import { communityBreadcrumbResolver } from '@dspace/core/breadcrumbs/community-breadcrumb.resolver'; import { i18nBreadcrumbResolver } from '@dspace/core/breadcrumbs/i18n-breadcrumb.resolver'; +import { rerunGuardsAndResolversOnPathChange } from '@dspace/core/router/utils/route.utils'; import { ObjectAuditLogsComponent } from '../audit-page/object-audit-overview/object-audit-logs.component'; import { browseByGuard } from '../browse-by/browse-by-guard'; @@ -52,7 +53,7 @@ export const ROUTES: Route[] = [ dso: communityPageResolver, breadcrumb: communityBreadcrumbResolver, }, - runGuardsAndResolvers: 'always', + runGuardsAndResolvers: rerunGuardsAndResolversOnPathChange, children: [ { path: COMMUNITY_EDIT_PATH, diff --git a/src/app/core/router/utils/route.utils.ts b/src/app/core/router/utils/route.utils.ts index 1b3eab67a18..c0167a628e9 100644 --- a/src/app/core/router/utils/route.utils.ts +++ b/src/app/core/router/utils/route.utils.ts @@ -2,10 +2,20 @@ import { ActivatedRouteSnapshot, Router, } from '@angular/router'; -import { hasValue } from '@dspace/shared/utils/empty.util'; +import { + hasNoValue, + hasValue, +} from '@dspace/shared/utils/empty.util'; import { URLCombiner } from '../../url-combiner/url-combiner'; +/** + * The id of the element marking the top of a search component, used as a + * URL fragment so the browser scrolls back to the search component (instead + * of the top of the page) after a search interaction updates the URL. + */ +export const SEARCH_COMPONENT_ANCHOR_ID = 'search-component'; + /** * Util function to retrieve the current path (without query parameters) the user is on * @param router The router service @@ -22,3 +32,31 @@ export function currentPathFromSnapshot(route: ActivatedRouteSnapshot): string { } return route.routeConfig ? route.routeConfig.path : ''; } + +/** + * Function to use as `runGuardsAndResolvers` on routes that contain embedded + * search components (e.g. item, collection and community pages). + * + * Resolvers and guards are only re-run when the path of the route itself, or + * of any of its activated (grand)child routes, actually changes. Query + * parameter changes caused by search interactions (e.g. selecting a filter or + * submitting the search form) no longer re-run the resolvers, which prevented + * the page from being fully reloaded on every search update. + * + * @param from The previously activated route snapshot + * @param to The newly activated route snapshot + */ +export function rerunGuardsAndResolversOnPathChange(from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot): boolean { + if (JSON.stringify(from.url) !== JSON.stringify(to.url)) { + return true; + } + const fromChild = from.firstChild; + const toChild = to.firstChild; + if (hasValue(fromChild) || hasValue(toChild)) { + if (hasNoValue(fromChild) || hasNoValue(toChild)) { + return true; + } + return rerunGuardsAndResolversOnPathChange(fromChild, toChild); + } + return false; +} diff --git a/src/app/item-page/item-page-routes.ts b/src/app/item-page/item-page-routes.ts index d6a6ab20ec1..4b71a1119fd 100644 --- a/src/app/item-page/item-page-routes.ts +++ b/src/app/item-page/item-page-routes.ts @@ -3,6 +3,7 @@ import { accessTokenResolver } from '@dspace/core/auth/access-token.resolver'; import { authenticatedGuard } from '@dspace/core/auth/authenticated.guard'; import { i18nBreadcrumbResolver } from '@dspace/core/breadcrumbs/i18n-breadcrumb.resolver'; import { itemBreadcrumbResolver } from '@dspace/core/breadcrumbs/item-breadcrumb.resolver'; +import { rerunGuardsAndResolversOnPathChange } from '@dspace/core/router/utils/route.utils'; import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths'; import { ObjectAuditLogsComponent } from '../audit-page/object-audit-overview/object-audit-logs.component'; @@ -50,7 +51,7 @@ export const ROUTES: Route[] = [ breadcrumb: itemBreadcrumbResolver, links: signpostingLinksResolver, }, - runGuardsAndResolvers: 'always', + runGuardsAndResolvers: rerunGuardsAndResolversOnPathChange, children: [ { path: '', diff --git a/src/app/shared/search-form/search-form.component.spec.ts b/src/app/shared/search-form/search-form.component.spec.ts index 87fa6c0bb23..e39ff9c3266 100644 --- a/src/app/shared/search-form/search-form.component.spec.ts +++ b/src/app/shared/search-form/search-form.component.spec.ts @@ -12,6 +12,7 @@ import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { DSpaceObjectDataService } from '@dspace/core/data/dspace-object-data.service'; import { PaginationService } from '@dspace/core/pagination/pagination.service'; +import { SEARCH_COMPONENT_ANCHOR_ID } from '@dspace/core/router/utils/route.utils'; import { Community } from '@dspace/core/shared/community.model'; import { DSpaceObject } from '@dspace/core/shared/dspace-object.model'; import { PaginationServiceStub } from '@dspace/core/testing/pagination-service.stub'; @@ -117,6 +118,7 @@ describe('SearchFormComponent', () => { expect(router.navigate).toHaveBeenCalledWith(comp.getSearchLinkParts(), { queryParams: { ...searchQuery, ...firstPage }, queryParamsHandling: 'merge', + fragment: SEARCH_COMPONENT_ANCHOR_ID, }); }); @@ -130,6 +132,7 @@ describe('SearchFormComponent', () => { expect(router.navigate).toHaveBeenCalledWith(comp.getSearchLinkParts(), { queryParams: { ...searchQuery, ...firstPage }, queryParamsHandling: 'merge', + fragment: SEARCH_COMPONENT_ANCHOR_ID, }); }); @@ -143,6 +146,7 @@ describe('SearchFormComponent', () => { expect(router.navigate).toHaveBeenCalledWith(comp.getSearchLinkParts(), { queryParams: { ...searchQuery, ...firstPage }, queryParamsHandling: 'merge', + fragment: SEARCH_COMPONENT_ANCHOR_ID, }); }); }); diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index 0bd6f2d8ebc..c557e38f890 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -11,7 +11,10 @@ import { Router } from '@angular/router'; import { DSONameService } from '@dspace/core/breadcrumbs/dso-name.service'; import { DSpaceObjectDataService } from '@dspace/core/data/dspace-object-data.service'; import { PaginationService } from '@dspace/core/pagination/pagination.service'; -import { currentPath } from '@dspace/core/router/utils/route.utils'; +import { + currentPath, + SEARCH_COMPONENT_ANCHOR_ID, +} from '@dspace/core/router/utils/route.utils'; import { DSpaceObject } from '@dspace/core/shared/dspace-object.model'; import { getFirstSucceededRemoteDataPayload } from '@dspace/core/shared/operators'; import { @@ -161,6 +164,7 @@ export class SearchFormComponent implements OnChanges { void this.router.navigate(this.getSearchLinkParts(), { queryParams: queryParams, queryParamsHandling: 'merge', + fragment: SEARCH_COMPONENT_ANCHOR_ID, }); } diff --git a/src/app/shared/search/advanced-search/advanced-search.component.ts b/src/app/shared/search/advanced-search/advanced-search.component.ts index fdae44a90cf..980a09c522e 100644 --- a/src/app/shared/search/advanced-search/advanced-search.component.ts +++ b/src/app/shared/search/advanced-search/advanced-search.component.ts @@ -15,7 +15,10 @@ import { APP_CONFIG, AppConfig, } from '@dspace/config/app-config.interface'; -import { currentPath } from '@dspace/core/router/utils/route.utils'; +import { + currentPath, + SEARCH_COMPONENT_ANCHOR_ID, +} from '@dspace/core/router/utils/route.utils'; import { FilterType } from '@dspace/core/shared/search/models/filter-type.model'; import { SearchFilterConfig } from '@dspace/core/shared/search/models/search-filter-config.model'; import { FilterConfig } from '@dspace/core/shared/search/search-filters/search-config.model'; @@ -151,6 +154,7 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { this.subs.push(this.searchConfigurationService.selectNewAppliedFilterParams(this.currentFilter, this.currentValue.trim(), this.currentOperator).pipe(take(1)).subscribe((params: Params) => { void this.router.navigate([this.getSearchLink()], { queryParams: params, + fragment: SEARCH_COMPONENT_ANCHOR_ID, }); this.currentValue = ''; })); diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html index 5bc7e62a35d..3dd7e2c8444 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html @@ -3,6 +3,7 @@ [tabIndex]="-1" [routerLink]="[searchLink]" [queryParams]="addQueryParams$ | async" + [fragment]="'search-component'" (click)="announceFilter(); filterService.minimizeAll()" rel="nofollow">