diff --git a/src/app/browse-by/browse-by-metadata/browse-by-metadata.component.ts b/src/app/browse-by/browse-by-metadata/browse-by-metadata.component.ts index 410f1d142ff..6a8312a45e0 100644 --- a/src/app/browse-by/browse-by-metadata/browse-by-metadata.component.ts +++ b/src/app/browse-by/browse-by-metadata/browse-by-metadata.component.ts @@ -82,7 +82,7 @@ export class BrowseByMetadataComponent extends AbstractBrowseByTypeComponent imp * The list of StartsWith options * Should be defined after ngOnInit is called! */ - startsWithOptions; + startsWithOptions: (string | number)[]; /** * The value we're browsing items for diff --git a/src/app/shared/browse-by/browse-by.component.html b/src/app/shared/browse-by/browse-by.component.html index 789399e6274..7d7e7266597 100644 --- a/src/app/shared/browse-by/browse-by.component.html +++ b/src/app/shared/browse-by/browse-by.component.html @@ -1,6 +1,7 @@

{{title | translate}}

- + +
diff --git a/src/app/shared/browse-by/browse-by.component.ts b/src/app/shared/browse-by/browse-by.component.ts index 6f61c030768..465d1e6cebd 100644 --- a/src/app/shared/browse-by/browse-by.component.ts +++ b/src/app/shared/browse-by/browse-by.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Injector, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Injector, Input, OnDestroy, OnInit, Output, OnChanges } from '@angular/core'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; @@ -6,7 +6,7 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options import { fadeIn, fadeInOut } from '../animations/fade'; import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; import { ListableObject } from '../object-collection/shared/listable-object.model'; -import { getStartsWithComponent, StartsWithType } from '../starts-with/starts-with-decorator'; +import { StartsWithType } from '../starts-with/starts-with-decorator'; import { PaginationService } from '../../core/pagination/pagination.service'; import { ViewMode } from '../../core/shared/view-mode.model'; import { RouteService } from '../../core/services/route.service'; @@ -26,7 +26,7 @@ import { TranslateService } from '@ngx-translate/core'; /** * Component to display a browse-by page for any ListableObject */ -export class BrowseByComponent implements OnInit, OnDestroy { +export class BrowseByComponent implements OnInit, OnChanges, OnDestroy { /** * ViewMode that should be passed to {@link ListableObjectComponentLoaderComponent}. @@ -67,7 +67,7 @@ export class BrowseByComponent implements OnInit, OnDestroy { /** * The list of options to render for the StartsWith component */ - @Input() startsWithOptions = []; + @Input() startsWithOptions: (string | number)[] = []; /** * Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination @@ -99,16 +99,6 @@ export class BrowseByComponent implements OnInit, OnDestroy { */ @Output() sortDirectionChange = new EventEmitter(); - /** - * An object injector used to inject the startsWithOptions to the switchable StartsWith component - */ - objectInjector: Injector; - - /** - * Declare SortDirection enumeration to use it in the template - */ - public sortDirections = SortDirection; - /** * Observable that tracks if the back button should be displayed based on the path parameters */ @@ -142,7 +132,7 @@ export class BrowseByComponent implements OnInit, OnDestroy { */ back = () => { const page = +this.previousPage$.value > 1 ? +this.previousPage$.value : 1; - this.paginationService.updateRoute(this.paginationConfig.id, {page: page}, {[this.paginationConfig.id + '.return']: null, value: null, startsWith: null}); + this.paginationService.updateRoute(this.paginationConfig.id, { page: page }, { [this.paginationConfig.id + '.return']: null, value: null, startsWith: null }); }; /** @@ -159,44 +149,19 @@ export class BrowseByComponent implements OnInit, OnDestroy { this.next.emit(true); } - /** - * Change the page size - * @param size - */ - doPageSizeChange(size) { - this.paginationService.updateRoute(this.paginationConfig.id,{pageSize: size}); - } - - /** - * Change the sort direction - * @param direction - */ - doSortDirectionChange(direction) { - this.paginationService.updateRoute(this.paginationConfig.id,{sortDirection: direction}); - } - - /** - * Get the switchable StartsWith component dependant on the type - */ - getStartsWithComponent() { - return getStartsWithComponent(this.type); - } - ngOnInit(): void { - this.objectInjector = Injector.create({ - providers: [ - { provide: 'startsWithOptions', useFactory: () => (this.startsWithOptions), deps:[] }, - { provide: 'paginationId', useFactory: () => (this.paginationConfig?.id), deps:[] } - ], - parent: this.injector - }); - const startsWith$ = this.routeService.getQueryParameterValue('startsWith'); const value$ = this.routeService.getQueryParameterValue('value'); this.shouldDisplayResetButton$ = observableCombineLatest([startsWith$, value$]).pipe( map(([startsWith, value]) => hasValue(startsWith) || hasValue(value)) ); + } + + ngOnChanges(): void { + if (this.sub) { + this.sub.unsubscribe(); + } this.sub = this.routeService.getQueryParameterValue(this.paginationConfig.id + '.return').subscribe(this.previousPage$); } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index c80bc8cd6d5..f8fb2fbb32b 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -282,6 +282,7 @@ import { NgxPaginationModule } from 'ngx-pagination'; import { ThemedUserMenuComponent } from './auth-nav-menu/user-menu/themed-user-menu.component'; import { ThemedLangSwitchComponent } from './lang-switch/themed-lang-switch.component'; import { DynamicComponentLoaderDirective } from './abstract-component-loader/dynamic-component-loader.directive'; +import { StartsWithLoaderComponent } from './starts-with/starts-with-loader.component'; const MODULES = [ CommonModule, @@ -377,7 +378,7 @@ const COMPONENTS = [ ThemedStatusBadgeComponent, BadgesComponent, ThemedBadgesComponent, - + StartsWithLoaderComponent, ItemSelectComponent, CollectionSelectComponent, MetadataRepresentationLoaderComponent, diff --git a/src/app/shared/starts-with/date/starts-with-date.component.spec.ts b/src/app/shared/starts-with/date/starts-with-date.component.spec.ts index 2407f21fdf3..4c26105890b 100644 --- a/src/app/shared/starts-with/date/starts-with-date.component.spec.ts +++ b/src/app/shared/starts-with/date/starts-with-date.component.spec.ts @@ -3,9 +3,8 @@ import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { TranslateModule } from '@ngx-translate/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { of as observableOf } from 'rxjs'; import { By } from '@angular/platform-browser'; import { StartsWithDateComponent } from './starts-with-date.component'; import { ActivatedRouteStub } from '../../testing/active-router.stub'; @@ -17,29 +16,25 @@ import { PaginationServiceStub } from '../../testing/pagination-service.stub'; describe('StartsWithDateComponent', () => { let comp: StartsWithDateComponent; let fixture: ComponentFixture; - let route: ActivatedRoute; - let router: Router; - let paginationService; - const options = [2019, 2018, 2017, 2016, 2015]; + let route: ActivatedRouteStub; + let paginationService: PaginationServiceStub; + let router: RouterStub; - const activatedRouteStub = Object.assign(new ActivatedRouteStub(), { - params: observableOf({}), - queryParams: observableOf({}) - }); + const options = [2019, 2018, 2017, 2016, 2015]; - paginationService = new PaginationServiceStub(); + beforeEach(waitForAsync(async () => { + route = new ActivatedRouteStub(); + router = new RouterStub(); + paginationService = new PaginationServiceStub(); - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ + await TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], declarations: [StartsWithDateComponent, EnumKeysPipe], providers: [ - { provide: 'startsWithOptions', useValue: options }, - { provide: 'paginationId', useValue: 'page-id' }, - { provide: ActivatedRoute, useValue: activatedRouteStub }, + { provide: ActivatedRoute, useValue: route }, { provide: PaginationService, useValue: paginationService }, - { provide: Router, useValue: new RouterStub() } + { provide: Router, useValue: router }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -48,9 +43,9 @@ describe('StartsWithDateComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(StartsWithDateComponent); comp = fixture.componentInstance; + comp.paginationId = 'page-id'; + comp.startsWithOptions = options; fixture.detectChanges(); - route = (comp as any).route; - router = (comp as any).router; }); it('should create a FormGroup containing a startsWith FormControl', () => { @@ -156,10 +151,6 @@ describe('StartsWithDateComponent', () => { describe('when filling in the input form', () => { let form; const expectedValue = '2015'; - const extras: NavigationExtras = { - queryParams: Object.assign({ startsWith: expectedValue }), - queryParamsHandling: 'merge' - }; beforeEach(() => { form = fixture.debugElement.query(By.css('form')); diff --git a/src/app/shared/starts-with/date/starts-with-date.component.ts b/src/app/shared/starts-with/date/starts-with-date.component.ts index 89d9361b6a0..34eda812aee 100644 --- a/src/app/shared/starts-with/date/starts-with-date.component.ts +++ b/src/app/shared/starts-with/date/starts-with-date.component.ts @@ -1,10 +1,7 @@ -import { Component, Inject } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; - +import { Component, OnInit } from '@angular/core'; import { renderStartsWithFor, StartsWithType } from '../starts-with-decorator'; import { StartsWithAbstractComponent } from '../starts-with-abstract.component'; import { hasValue } from '../../empty.util'; -import { PaginationService } from '../../../core/pagination/pagination.service'; /** * A switchable component rendering StartsWith options for the type "Date". @@ -16,7 +13,7 @@ import { PaginationService } from '../../../core/pagination/pagination.service'; templateUrl: './starts-with-date.component.html' }) @renderStartsWithFor(StartsWithType.date) -export class StartsWithDateComponent extends StartsWithAbstractComponent { +export class StartsWithDateComponent extends StartsWithAbstractComponent implements OnInit { /** * A list of options for months to select from @@ -33,14 +30,6 @@ export class StartsWithDateComponent extends StartsWithAbstractComponent { */ startsWithYear: number; - public constructor(@Inject('startsWithOptions') public startsWithOptions: any[], - @Inject('paginationId') public paginationId: string, - protected paginationService: PaginationService, - protected route: ActivatedRoute, - protected router: Router) { - super(startsWithOptions, paginationId, paginationService, route, router); - } - ngOnInit() { this.monthOptions = [ 'none', @@ -133,13 +122,6 @@ export class StartsWithDateComponent extends StartsWithAbstractComponent { } } - /** - * Get startsWithYear as a number; - */ - getStartsWithYear() { - return this.startsWithYear; - } - /** * Get startsWithMonth as a number; */ diff --git a/src/app/shared/starts-with/starts-with-abstract.component.ts b/src/app/shared/starts-with/starts-with-abstract.component.ts index ad9c56c9702..22e069e2fbe 100644 --- a/src/app/shared/starts-with/starts-with-abstract.component.ts +++ b/src/app/shared/starts-with/starts-with-abstract.component.ts @@ -1,9 +1,10 @@ -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit, Input } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { Subscription } from 'rxjs'; import { UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { hasValue } from '../empty.util'; import { PaginationService } from '../../core/pagination/pagination.service'; +import { StartsWithType } from './starts-with-decorator'; /** * An abstract component to render StartsWith options @@ -13,6 +14,13 @@ import { PaginationService } from '../../core/pagination/pagination.service'; template: '' }) export abstract class StartsWithAbstractComponent implements OnInit, OnDestroy { + + @Input() paginationId: string; + + @Input() startsWithOptions: (string | number)[]; + + @Input() type: StartsWithType; + /** * The currently selected startsWith in string format */ @@ -28,11 +36,11 @@ export abstract class StartsWithAbstractComponent implements OnInit, OnDestroy { */ subs: Subscription[] = []; - public constructor(@Inject('startsWithOptions') public startsWithOptions: any[], - @Inject('paginationId') public paginationId: string, - protected paginationService: PaginationService, - protected route: ActivatedRoute, - protected router: Router) { + public constructor( + protected paginationService: PaginationService, + protected route: ActivatedRoute, + protected router: Router, + ) { } ngOnInit(): void { @@ -55,15 +63,6 @@ export abstract class StartsWithAbstractComponent implements OnInit, OnDestroy { return this.startsWith; } - /** - * Set the startsWith by event - * @param event - */ - setStartsWithEvent(event: Event) { - this.startsWith = (event.target as HTMLInputElement).value; - this.setStartsWithParam(); - } - /** * Set the startsWith by string * @param startsWith @@ -82,7 +81,7 @@ export abstract class StartsWithAbstractComponent implements OnInit, OnDestroy { if (resetPage) { this.paginationService.updateRoute(this.paginationId, {page: 1}, { startsWith: this.startsWith }); } else { - this.router.navigate([], { + void this.router.navigate([], { queryParams: Object.assign({ startsWith: this.startsWith }), queryParamsHandling: 'merge' }); diff --git a/src/app/shared/starts-with/starts-with-loader.component.spec.ts b/src/app/shared/starts-with/starts-with-loader.component.spec.ts new file mode 100644 index 00000000000..7eb0ec8819b --- /dev/null +++ b/src/app/shared/starts-with/starts-with-loader.component.spec.ts @@ -0,0 +1,71 @@ +import { StartsWithLoaderComponent } from './starts-with-loader.component'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; +import { DynamicComponentLoaderDirective } from '../abstract-component-loader/dynamic-component-loader.directive'; +import { TranslateModule } from '@ngx-translate/core'; +import { StartsWithTextComponent } from './text/starts-with-text.component'; +import { Router, ActivatedRoute } from '@angular/router'; +import { RouterStub } from '../testing/router.stub'; +import { ThemeService } from '../theme-support/theme.service'; +import { getMockThemeService } from '../mocks/theme-service.mock'; +import { StartsWithType } from './starts-with-decorator'; +import { ActivatedRouteStub } from '../testing/active-router.stub'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../testing/pagination-service.stub'; + +describe('StartsWithLoaderComponent', () => { + let comp: StartsWithLoaderComponent; + let fixture: ComponentFixture; + + let paginationService: PaginationServiceStub; + let route: ActivatedRouteStub; + let themeService: ThemeService; + + const type: StartsWithType = StartsWithType.text; + + beforeEach(waitForAsync(() => { + paginationService = new PaginationServiceStub(); + route = new ActivatedRouteStub(); + themeService = getMockThemeService('dspace'); + + void TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), + ], + declarations: [ + StartsWithLoaderComponent, + StartsWithTextComponent, + DynamicComponentLoaderDirective, + ], + providers: [ + { provide: PaginationService, useValue: paginationService }, + { provide: ActivatedRoute, useValue: route }, + { provide: Router, useValue: new RouterStub() }, + { provide: ThemeService, useValue: themeService }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).overrideComponent(StartsWithLoaderComponent, { + set: { + changeDetection: ChangeDetectionStrategy.Default, + entryComponents: [StartsWithTextComponent], + } + }).compileComponents(); + })); + + beforeEach(waitForAsync(() => { + fixture = TestBed.createComponent(StartsWithLoaderComponent); + comp = fixture.componentInstance; + comp.type = type; + comp.paginationId = 'bbm'; + comp.startsWithOptions = []; + spyOn(comp, 'getComponent').and.returnValue(StartsWithTextComponent); + + fixture.detectChanges(); + })); + + describe('When the component is rendered', () => { + it('should call the getComponent function', () => { + expect(comp.getComponent).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/app/shared/starts-with/starts-with-loader.component.ts b/src/app/shared/starts-with/starts-with-loader.component.ts new file mode 100644 index 00000000000..085daa4dd0e --- /dev/null +++ b/src/app/shared/starts-with/starts-with-loader.component.ts @@ -0,0 +1,33 @@ +import { Component, Input, } from '@angular/core'; +import { AbstractComponentLoaderComponent } from '../abstract-component-loader/abstract-component-loader.component'; +import { GenericConstructor } from '../../core/shared/generic-constructor'; +import { getStartsWithComponent, StartsWithType } from './starts-with-decorator'; +import { StartsWithAbstractComponent } from './starts-with-abstract.component'; + +/** + * Component for loading a {@link StartsWithAbstractComponent} depending on the "type" input + */ +@Component({ + selector: 'ds-starts-with-loader', + templateUrl: '../abstract-component-loader/abstract-component-loader.component.html', +}) +export class StartsWithLoaderComponent extends AbstractComponentLoaderComponent { + + @Input() paginationId: string; + + @Input() startsWithOptions: (string | number)[]; + + @Input() type: StartsWithType; + + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, + 'paginationId', + 'startsWithOptions', + 'type', + ]; + + public getComponent(): GenericConstructor { + return getStartsWithComponent(this.type); + } + +} diff --git a/src/app/shared/starts-with/text/starts-with-text.component.spec.ts b/src/app/shared/starts-with/text/starts-with-text.component.spec.ts index b717c72d76b..e282294090e 100644 --- a/src/app/shared/starts-with/text/starts-with-text.component.spec.ts +++ b/src/app/shared/starts-with/text/starts-with-text.component.spec.ts @@ -10,25 +10,31 @@ import { By } from '@angular/platform-browser'; import { StartsWithTextComponent } from './starts-with-text.component'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../testing/pagination-service.stub'; +import { ActivatedRouteStub } from '../../testing/active-router.stub'; +import { RouterStub } from '../../testing/router.stub'; describe('StartsWithTextComponent', () => { let comp: StartsWithTextComponent; let fixture: ComponentFixture; - let route: ActivatedRoute; - let router: Router; + + let paginationService: PaginationServiceStub; + let route: ActivatedRouteStub; + let router: RouterStub; const options = ['0-9', 'A', 'B', 'C']; - const paginationService = new PaginationServiceStub(); + beforeEach(waitForAsync(async () => { + paginationService = new PaginationServiceStub(); + route = new ActivatedRouteStub(); + router = new RouterStub(); - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ + await TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], declarations: [StartsWithTextComponent, EnumKeysPipe], providers: [ - { provide: 'startsWithOptions', useValue: options }, - { provide: 'paginationId', useValue: 'page-id' }, - { provide: PaginationService, useValue: paginationService } + { provide: PaginationService, useValue: paginationService }, + { provide: ActivatedRoute, useValue: route }, + { provide: Router, useValue: router }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -37,10 +43,9 @@ describe('StartsWithTextComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(StartsWithTextComponent); comp = fixture.componentInstance; + comp.paginationId = 'page-id'; + comp.startsWithOptions = options; fixture.detectChanges(); - route = (comp as any).route; - router = (comp as any).router; - spyOn(router, 'navigate'); }); it('should create a FormGroup containing a startsWith FormControl', () => { diff --git a/src/app/shared/starts-with/text/starts-with-text.component.ts b/src/app/shared/starts-with/text/starts-with-text.component.ts index 9f44fc4d40b..592f973aa68 100644 --- a/src/app/shared/starts-with/text/starts-with-text.component.ts +++ b/src/app/shared/starts-with/text/starts-with-text.component.ts @@ -35,15 +35,4 @@ export class StartsWithTextComponent extends StartsWithAbstractComponent { super.setStartsWithParam(resetPage); } - /** - * Checks whether the provided option is equal to the current startsWith - * @param option - */ - isSelectedOption(option: string): boolean { - if (this.startsWith === '0' && option === '0-9') { - return true; - } - return option === this.startsWith; - } - }