Skip to content

Commit 08bda53

Browse files
authored
Merge pull request DSpace#3907 from 4Science/task/dspace-7_x/DURACOM-303
[Port dspace-7_x] Exclude search and browse from Angular SSR
2 parents c7d8949 + 8a4f24f commit 08bda53

38 files changed

Lines changed: 604 additions & 43 deletions

config/config.example.yml

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ ssr:
2525
inlineCriticalCss: false
2626
# Path prefixes to enable SSR for. By default these are limited to paths of primary DSpace objects.
2727
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ]
28+
# Whether to enable rendering of Search component on SSR.
29+
# If set to true the component will be included in the HTML returned from the server side rendering.
30+
# If set to false the component will not be included in the HTML returned from the server side rendering.
31+
enableSearchComponent: false,
32+
# Whether to enable rendering of Browse component on SSR.
33+
# If set to true the component will be included in the HTML returned from the server side rendering.
34+
# If set to false the component will not be included in the HTML returned from the server side rendering.
35+
enableBrowseComponent: false,
2836

2937
# The REST API server settings
3038
# NOTE: these settings define which (publicly available) REST API to use. They are usually
@@ -84,7 +92,7 @@ cache:
8492
anonymousCache:
8593
# Maximum number of pages to cache. Default is zero (0) which means anonymous user cache is disabled.
8694
# As all pages are cached in server memory, increasing this value will increase memory needs.
87-
# Individual cached pages are usually small (<100KB), so a value of max=1000 would only require ~100MB of memory.
95+
# Individual cached pages are usually small (<100KB), so a value of max=1000 would only require ~100MB of memory.
8896
max: 0
8997
# Amount of time after which cached pages are considered stale (in ms). After becoming stale, the cached
9098
# copy is automatically refreshed on the next request.
@@ -394,7 +402,7 @@ vocabularies:
394402
vocabulary: 'srsc'
395403
enabled: true
396404

397-
# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query.
405+
# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query.
398406
comcolSelectionSort:
399407
sortField: 'dc.title'
400408
sortDirection: 'ASC'
@@ -412,3 +420,12 @@ liveRegion:
412420
messageTimeOutDurationMs: 30000
413421
# The visibility of the live region. Setting this to true is only useful for debugging purposes.
414422
isVisible: false
423+
424+
425+
# Search settings
426+
search:
427+
# Number used to render n UI elements called loading skeletons that act as placeholders.
428+
# These elements indicate that some content will be loaded in their stead.
429+
# Since we don't know how many filters will be loaded before we receive a response from the server we use this parameter for the skeletons count.
430+
# e.g. If we set 5 then 5 loading skeletons will be visualized before the actual filters are retrieved.
431+
defaultFiltersCount: 5

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
"ngx-infinite-scroll": "^15.0.0",
112112
"ngx-pagination": "6.0.3",
113113
"ngx-sortablejs": "^11.1.0",
114+
"ngx-skeleton-loader": "^7.0.0",
114115
"ngx-ui-switch": "^14.1.0",
115116
"nouislider": "^15.8.1",
116117
"pem": "1.14.8",

src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BrowseByDatePageComponent } from './browse-by-date-page.component';
2-
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2+
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
33
import { CommonModule } from '@angular/common';
44
import { RouterTestingModule } from '@angular/router/testing';
55
import { TranslateModule } from '@ngx-translate/core';
@@ -9,7 +9,7 @@ import { ActivatedRoute, Router } from '@angular/router';
99
import { BrowseService } from '../../core/browse/browse.service';
1010
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
1111
import { RouterMock } from '../../shared/mocks/router.mock';
12-
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
12+
import { ChangeDetectorRef, NO_ERRORS_SCHEMA, PLATFORM_ID } from '@angular/core';
1313
import { of as observableOf } from 'rxjs';
1414
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
1515
import { Community } from '../../core/shared/community.model';
@@ -24,6 +24,8 @@ import { APP_CONFIG } from '../../../config/app-config.interface';
2424
import { environment } from '../../../environments/environment';
2525
import { SortDirection } from '../../core/cache/models/sort-options.model';
2626
import { cold } from 'jasmine-marbles';
27+
import { Store } from '@ngrx/store';
28+
import { BrowseEntry } from '../../core/shared/browse-entry.model';
2729

2830
describe('BrowseByDatePageComponent', () => {
2931
let comp: BrowseByDatePageComponent;
@@ -95,7 +97,10 @@ describe('BrowseByDatePageComponent', () => {
9597
{ provide: Router, useValue: new RouterMock() },
9698
{ provide: PaginationService, useValue: paginationService },
9799
{ provide: ChangeDetectorRef, useValue: mockCdRef },
98-
{ provide: APP_CONFIG, useValue: environment }
100+
{ provide: APP_CONFIG, useValue: environment },
101+
{ provide: Store, useValue: {} },
102+
{ provide: APP_CONFIG, useValue: environment },
103+
{ provide: PLATFORM_ID, useValue: 'browser' },
99104
],
100105
schemas: [NO_ERRORS_SCHEMA]
101106
}).compileComponents();
@@ -131,4 +136,33 @@ describe('BrowseByDatePageComponent', () => {
131136
//expect(comp.startsWithOptions[0]).toEqual(new Date().getUTCFullYear());
132137
expect(comp.startsWithOptions[0]).toEqual(1960);
133138
});
139+
140+
describe('when rendered in SSR', () => {
141+
beforeEach(() => {
142+
comp.platformId = 'server';
143+
spyOn((comp as any).browseService, 'getBrowseItemsFor');
144+
});
145+
146+
it('should not call getBrowseItemsFor on init', (done) => {
147+
comp.ngOnInit();
148+
expect((comp as any).browseService.getBrowseItemsFor).not.toHaveBeenCalled();
149+
comp.loading$.subscribe((res) => {
150+
expect(res).toBeFalsy();
151+
done();
152+
});
153+
});
154+
});
155+
156+
describe('when rendered in CSR', () => {
157+
beforeEach(() => {
158+
comp.platformId = 'browser';
159+
spyOn((comp as any).browseService, 'getBrowseItemsFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
160+
});
161+
162+
it('should call getBrowseItemsFor on init', fakeAsync(() => {
163+
comp.ngOnInit();
164+
tick(100);
165+
expect((comp as any).browseService.getBrowseItemsFor).toHaveBeenCalled();
166+
}));
167+
});
134168
});

src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChangeDetectorRef, Component, Inject } from '@angular/core';
1+
import { ChangeDetectorRef, Component, Inject, PLATFORM_ID } from '@angular/core';
22
import {
33
BrowseByMetadataPageComponent,
44
browseParamsToOptions
@@ -11,13 +11,16 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv
1111
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
1212
import { PaginationService } from '../../core/pagination/pagination.service';
1313
import { map, take } from 'rxjs/operators';
14+
import { of as observableOf } from 'rxjs';
1415
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
1516
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
1617
import { isValidDate } from '../../shared/date.util';
1718
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
1819
import { RemoteData } from '../../core/data/remote-data';
1920
import { Item } from '../../core/shared/item.model';
2021
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
22+
import { isPlatformServer } from '@angular/common';
23+
import { environment } from '../../../environments/environment';
2124

2225
@Component({
2326
selector: 'ds-browse-by-date-page',
@@ -44,11 +47,16 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
4447
protected cdRef: ChangeDetectorRef,
4548
@Inject(APP_CONFIG) public appConfig: AppConfig,
4649
public dsoNameService: DSONameService,
50+
@Inject(PLATFORM_ID) public platformId: any,
4751
) {
48-
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService);
52+
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService, platformId);
4953
}
5054

5155
ngOnInit(): void {
56+
if (!this.renderOnServerSide && !environment.universal.enableBrowseComponent && isPlatformServer(this.platformId)) {
57+
this.loading$ = observableOf(false);
58+
return;
59+
}
5260
const sortConfig = new SortOptions('default', SortDirection.ASC);
5361
this.startsWithType = StartsWithType.date;
5462
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);

src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<div class="container">
1+
<div class="container" *ngIf="(!ssrRenderingDisabled)">
22
<ng-container *ngVar="(parent$ | async) as parent">
33
<ng-container *ngIf="parent?.payload as parentContext">
44
<div class="d-flex flex-row border-bottom mb-4 pb-4">

src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.spec.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
browseParamsToOptions,
44
getBrowseSearchOptions
55
} from './browse-by-metadata-page.component';
6-
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
6+
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
77
import { BrowseService } from '../../core/browse/browse.service';
88
import { CommonModule } from '@angular/common';
99
import { RouterTestingModule } from '@angular/router/testing';
@@ -13,7 +13,7 @@ import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe';
1313
import { ActivatedRoute, Router } from '@angular/router';
1414
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
1515
import { Observable, of as observableOf } from 'rxjs';
16-
import { NO_ERRORS_SCHEMA } from '@angular/core';
16+
import { NO_ERRORS_SCHEMA, PLATFORM_ID } from '@angular/core';
1717
import { RemoteData } from '../../core/data/remote-data';
1818
import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model';
1919
import { PageInfo } from '../../core/shared/page-info.model';
@@ -111,7 +111,8 @@ describe('BrowseByMetadataPageComponent', () => {
111111
{ provide: DSpaceObjectDataService, useValue: mockDsoService },
112112
{ provide: PaginationService, useValue: paginationService },
113113
{ provide: Router, useValue: new RouterMock() },
114-
{ provide: APP_CONFIG, useValue: environmentMock }
114+
{ provide: APP_CONFIG, useValue: environmentMock },
115+
{ provide: PLATFORM_ID, useValue: 'browser' },
115116
],
116117
schemas: [NO_ERRORS_SCHEMA]
117118
}).compileComponents();
@@ -224,6 +225,35 @@ describe('BrowseByMetadataPageComponent', () => {
224225
expect(result.fetchThumbnail).toBeTrue();
225226
});
226227
});
228+
229+
describe('when rendered in SSR', () => {
230+
beforeEach(() => {
231+
comp.ssrRenderingDisabled = true;
232+
spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(null));
233+
});
234+
235+
it('should not call getBrowseEntriesFor on init', (done) => {
236+
comp.ngOnInit();
237+
expect((comp as any).browseService.getBrowseEntriesFor).not.toHaveBeenCalled();
238+
comp.loading$.subscribe((res) => {
239+
expect(res).toBeFalsy();
240+
done();
241+
});
242+
});
243+
});
244+
245+
describe('when rendered in CSR', () => {
246+
beforeEach(() => {
247+
comp.ssrRenderingDisabled = false;
248+
spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
249+
});
250+
251+
it('should call getBrowseEntriesFor on init', fakeAsync(() => {
252+
comp.ngOnInit();
253+
tick(100);
254+
expect((comp as any).browseService.getBrowseEntriesFor).toHaveBeenCalled();
255+
}));
256+
});
227257
});
228258

229259
export function toRemoteData(objects: any[]): Observable<RemoteData<PaginatedList<any>>> {

src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { combineLatest as observableCombineLatest, Observable, Subscription, of as observableOf } from 'rxjs';
2-
import { Component, Inject, OnInit, OnDestroy } from '@angular/core';
2+
import { Component, Inject, OnInit, OnDestroy, Input, PLATFORM_ID } from '@angular/core';
33
import { RemoteData } from '../../core/data/remote-data';
44
import { PaginatedList } from '../../core/data/paginated-list.model';
55
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
@@ -22,6 +22,8 @@ import { Collection } from '../../core/shared/collection.model';
2222
import { Community } from '../../core/shared/community.model';
2323
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
2424
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
25+
import { isPlatformServer } from '@angular/common';
26+
import { environment } from '../../../environments/environment';
2527

2628
export const BBM_PAGINATION_ID = 'bbm';
2729

@@ -37,7 +39,10 @@ export const BBM_PAGINATION_ID = 'bbm';
3739
* 'dc.contributor.*'
3840
*/
3941
export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
40-
42+
/**
43+
* Defines whether to fetch search results during SSR execution
44+
*/
45+
@Input() renderOnServerSide = false;
4146
/**
4247
* The list of browse-entries to display
4348
*/
@@ -126,6 +131,10 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
126131
* Observable determining if the loading animation needs to be shown
127132
*/
128133
loading$ = observableOf(true);
134+
/**
135+
* Whether this component should be rendered or not in SSR
136+
*/
137+
ssrRenderingDisabled = false;
129138

130139
public constructor(protected route: ActivatedRoute,
131140
protected browseService: BrowseService,
@@ -134,6 +143,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
134143
protected router: Router,
135144
@Inject(APP_CONFIG) public appConfig: AppConfig,
136145
public dsoNameService: DSONameService,
146+
@Inject(PLATFORM_ID) public platformId: any,
137147
) {
138148

139149
this.fetchThumbnails = this.appConfig.browseBy.showThumbnails;
@@ -142,11 +152,15 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
142152
currentPage: 1,
143153
pageSize: this.appConfig.browseBy.pageSize,
144154
});
145-
}
155+
this.ssrRenderingDisabled = !this.renderOnServerSide && !environment.universal.enableBrowseComponent && isPlatformServer(this.platformId);
156+
}
146157

147158

148159
ngOnInit(): void {
149-
160+
if (this.ssrRenderingDisabled) {
161+
this.loading$ = observableOf(false);
162+
return;
163+
}
150164
const sortConfig = new SortOptions('default', SortDirection.ASC);
151165
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig));
152166
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);

src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
1+
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
22
import { ActivatedRoute, Router } from '@angular/router';
33
import { Item } from '../../core/shared/item.model';
44
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
@@ -22,6 +22,7 @@ import { PaginationService } from '../../core/pagination/pagination.service';
2222
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
2323
import { APP_CONFIG } from '../../../config/app-config.interface';
2424
import { environment } from '../../../environments/environment';
25+
import { BrowseEntry } from '../../core/shared/browse-entry.model';
2526

2627

2728
describe('BrowseByTitlePageComponent', () => {
@@ -63,7 +64,8 @@ describe('BrowseByTitlePageComponent', () => {
6364

6465
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
6566
params: observableOf({}),
66-
data: observableOf({ metadata: 'title' })
67+
queryParams: observableOf({}),
68+
data: observableOf({ metadata: 'title' }),
6769
});
6870

6971
const paginationService = new PaginationServiceStub();
@@ -97,4 +99,35 @@ describe('BrowseByTitlePageComponent', () => {
9799
expect(result.payload.page).toEqual(mockItems);
98100
});
99101
});
102+
103+
describe('when rendered in SSR', () => {
104+
beforeEach(() => {
105+
comp.platformId = 'server';
106+
spyOn((comp as any).browseService, 'getBrowseItemsFor');
107+
fixture.detectChanges();
108+
});
109+
110+
it('should not call getBrowseItemsFor on init', (done) => {
111+
comp.ngOnInit();
112+
expect((comp as any).browseService.getBrowseItemsFor).not.toHaveBeenCalled();
113+
comp.loading$.subscribe((res) => {
114+
expect(res).toBeFalsy();
115+
done();
116+
});
117+
});
118+
});
119+
120+
describe('when rendered in CSR', () => {
121+
beforeEach(() => {
122+
comp.platformId = 'browser';
123+
fixture.detectChanges();
124+
spyOn((comp as any).browseService, 'getBrowseItemsFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
125+
});
126+
127+
it('should call getBrowseItemsFor on init', fakeAsync(() => {
128+
comp.ngOnInit();
129+
tick(100);
130+
expect((comp as any).browseService.getBrowseItemsFor).toHaveBeenCalled();
131+
}));
132+
});
100133
});

src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { combineLatest as observableCombineLatest } from 'rxjs';
2-
import { Component, Inject } from '@angular/core';
2+
import { Component, Inject, PLATFORM_ID } from '@angular/core';
33
import { ActivatedRoute, Params, Router } from '@angular/router';
44
import { hasValue } from '../../shared/empty.util';
55
import {
@@ -11,9 +11,12 @@ import { BrowseService } from '../../core/browse/browse.service';
1111
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
1212
import { PaginationService } from '../../core/pagination/pagination.service';
1313
import { map, take } from 'rxjs/operators';
14+
import { of as observableOf } from 'rxjs';
1415
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
1516
import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface';
1617
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
18+
import { isPlatformServer } from '@angular/common';
19+
import { environment } from '../../../environments/environment';
1720

1821
@Component({
1922
selector: 'ds-browse-by-title-page',
@@ -32,11 +35,16 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
3235
protected router: Router,
3336
@Inject(APP_CONFIG) public appConfig: AppConfig,
3437
public dsoNameService: DSONameService,
38+
@Inject(PLATFORM_ID) public platformId: any,
3539
) {
36-
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService);
40+
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService, platformId);
3741
}
3842

3943
ngOnInit(): void {
44+
if (!this.renderOnServerSide && !environment.universal.enableBrowseComponent && isPlatformServer(this.platformId)) {
45+
this.loading$ = observableOf(false);
46+
return;
47+
}
4048
const sortConfig = new SortOptions('dc.title', SortDirection.ASC);
4149
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
4250
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);

0 commit comments

Comments
 (0)