Skip to content

Commit 63c9874

Browse files
authored
Merge pull request #3886 from tdonohue/port_3709_to_main
[Port main] Exclude search and browse from Angular SSR (#3709)
2 parents cb8a7cd + 17ecc59 commit 63c9874

38 files changed

+641
-51
lines changed

config/config.example.yml

Lines changed: 14 additions & 0 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
@@ -450,6 +458,12 @@ search:
450458
enabled: false
451459
# List of filters to enable in "Advanced Search" dropdown
452460
filter: [ 'title', 'author', 'subject', 'entityType' ]
461+
#
462+
# Number used to render n UI elements called loading skeletons that act as placeholders.
463+
# These elements indicate that some content will be loaded in their stead.
464+
# 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.
465+
# e.g. If we set 5 then 5 loading skeletons will be visualized before the actual filters are retrieved.
466+
defaultFiltersCount: 5
453467

454468

455469
# Notify metrics

package-lock.json

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154
"ng2-nouislider": "^2.0.0",
155155
"ngx-infinite-scroll": "^16.0.0",
156156
"ngx-pagination": "6.0.3",
157+
"ngx-skeleton-loader": "^9.0.0",
157158
"ngx-ui-switch": "^14.1.0",
158159
"nouislider": "^15.7.1",
159160
"orejime": "^2.3.1",

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import { CommonModule } from '@angular/common';
22
import {
33
ChangeDetectorRef,
44
NO_ERRORS_SCHEMA,
5+
PLATFORM_ID,
56
} from '@angular/core';
67
import {
78
ComponentFixture,
9+
fakeAsync,
810
TestBed,
11+
tick,
912
waitForAsync,
1013
} from '@angular/core/testing';
1114
import {
@@ -26,6 +29,7 @@ import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-
2629
import { SortDirection } from '../../core/cache/models/sort-options.model';
2730
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
2831
import { PaginationService } from '../../core/pagination/pagination.service';
32+
import { BrowseEntry } from '../../core/shared/browse-entry.model';
2933
import { Community } from '../../core/shared/community.model';
3034
import { Item } from '../../core/shared/item.model';
3135
import { ThemedBrowseByComponent } from '../../shared/browse-by/themed-browse-by.component';
@@ -123,6 +127,7 @@ describe('BrowseByDateComponent', () => {
123127
{ provide: ChangeDetectorRef, useValue: mockCdRef },
124128
{ provide: Store, useValue: {} },
125129
{ provide: APP_CONFIG, useValue: environment },
130+
{ provide: PLATFORM_ID, useValue: 'browser' },
126131
],
127132
schemas: [NO_ERRORS_SCHEMA],
128133
})
@@ -172,4 +177,33 @@ describe('BrowseByDateComponent', () => {
172177
//expect(comp.startsWithOptions[0]).toEqual(new Date().getUTCFullYear());
173178
expect(comp.startsWithOptions[0]).toEqual(1960);
174179
});
180+
181+
describe('when rendered in SSR', () => {
182+
beforeEach(() => {
183+
comp.platformId = 'server';
184+
spyOn((comp as any).browseService, 'getBrowseItemsFor');
185+
});
186+
187+
it('should not call getBrowseItemsFor on init', (done) => {
188+
comp.ngOnInit();
189+
expect((comp as any).browseService.getBrowseItemsFor).not.toHaveBeenCalled();
190+
comp.loading$.subscribe((res) => {
191+
expect(res).toBeFalsy();
192+
done();
193+
});
194+
});
195+
});
196+
197+
describe('when rendered in CSR', () => {
198+
beforeEach(() => {
199+
comp.platformId = 'browser';
200+
spyOn((comp as any).browseService, 'getBrowseItemsFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
201+
});
202+
203+
it('should call getBrowseItemsFor on init', fakeAsync(() => {
204+
comp.ngOnInit();
205+
tick(100);
206+
expect((comp as any).browseService.getBrowseItemsFor).toHaveBeenCalled();
207+
}));
208+
});
175209
});

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import {
22
AsyncPipe,
3+
isPlatformServer,
34
NgIf,
45
} from '@angular/common';
56
import {
67
ChangeDetectorRef,
78
Component,
89
Inject,
910
OnInit,
11+
PLATFORM_ID,
1012
} from '@angular/core';
1113
import {
1214
ActivatedRoute,
@@ -17,6 +19,7 @@ import { TranslateModule } from '@ngx-translate/core';
1719
import {
1820
combineLatest as observableCombineLatest,
1921
Observable,
22+
of as observableOf,
2023
} from 'rxjs';
2124
import {
2225
map,
@@ -28,6 +31,7 @@ import {
2831
APP_CONFIG,
2932
AppConfig,
3033
} from '../../../config/app-config.interface';
34+
import { environment } from '../../../environments/environment';
3135
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
3236
import { BrowseService } from '../../core/browse/browse.service';
3337
import {
@@ -99,11 +103,16 @@ export class BrowseByDateComponent extends BrowseByMetadataComponent implements
99103
@Inject(APP_CONFIG) public appConfig: AppConfig,
100104
public dsoNameService: DSONameService,
101105
protected cdRef: ChangeDetectorRef,
106+
@Inject(PLATFORM_ID) public platformId: any,
102107
) {
103-
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService);
108+
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService, platformId);
104109
}
105110

106111
ngOnInit(): void {
112+
if (!this.renderOnServerSide && !environment.ssr.enableBrowseComponent && isPlatformServer(this.platformId)) {
113+
this.loading$ = observableOf(false);
114+
return;
115+
}
107116
const sortConfig = new SortOptions('default', SortDirection.ASC);
108117
this.startsWithType = StartsWithType.date;
109118
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<section class="comcol-page-browse-section">
1+
<section class="comcol-page-browse-section" *ngIf="(!ssrRenderingDisabled)">
22
<div class="browse-by-metadata w-100">
33
<ds-browse-by *ngIf="(loading$ | async) !== true" class="col-xs-12 w-100"
44
title="{{'browse.title' | translate:{

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

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { CommonModule } from '@angular/common';
2-
import { NO_ERRORS_SCHEMA } from '@angular/core';
2+
import {
3+
NO_ERRORS_SCHEMA,
4+
PLATFORM_ID,
5+
} from '@angular/core';
36
import {
47
ComponentFixture,
8+
fakeAsync,
59
TestBed,
10+
tick,
611
waitForAsync,
712
} from '@angular/core/testing';
813
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@@ -147,6 +152,7 @@ describe('BrowseByMetadataComponent', () => {
147152
{ provide: ThemeService, useValue: getMockThemeService() },
148153
{ provide: SelectableListService, useValue: {} },
149154
{ provide: HostWindowService, useValue: {} },
155+
{ provide: PLATFORM_ID, useValue: 'browser' },
150156
],
151157
schemas: [NO_ERRORS_SCHEMA],
152158
})
@@ -259,6 +265,35 @@ describe('BrowseByMetadataComponent', () => {
259265
expect(result.fetchThumbnail).toBeTrue();
260266
});
261267
});
268+
269+
describe('when rendered in SSR', () => {
270+
beforeEach(() => {
271+
comp.ssrRenderingDisabled = true;
272+
spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(null));
273+
});
274+
275+
it('should not call getBrowseEntriesFor on init', (done) => {
276+
comp.ngOnInit();
277+
expect((comp as any).browseService.getBrowseEntriesFor).not.toHaveBeenCalled();
278+
comp.loading$.subscribe((res) => {
279+
expect(res).toBeFalsy();
280+
done();
281+
});
282+
});
283+
});
284+
285+
describe('when rendered in CSR', () => {
286+
beforeEach(() => {
287+
comp.ssrRenderingDisabled = false;
288+
spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
289+
});
290+
291+
it('should call getBrowseEntriesFor on init', fakeAsync(() => {
292+
comp.ngOnInit();
293+
tick(100);
294+
expect((comp as any).browseService.getBrowseEntriesFor).toHaveBeenCalled();
295+
}));
296+
});
262297
});
263298

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

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
AsyncPipe,
3+
isPlatformServer,
34
NgIf,
45
} from '@angular/common';
56
import {
@@ -9,6 +10,7 @@ import {
910
OnChanges,
1011
OnDestroy,
1112
OnInit,
13+
PLATFORM_ID,
1214
} from '@angular/core';
1315
import {
1416
ActivatedRoute,
@@ -33,6 +35,7 @@ import {
3335
APP_CONFIG,
3436
AppConfig,
3537
} from '../../../config/app-config.interface';
38+
import { environment } from '../../../environments/environment';
3639
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
3740
import { BrowseService } from '../../core/browse/browse.service';
3841
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
@@ -114,6 +117,11 @@ export class BrowseByMetadataComponent implements OnInit, OnChanges, OnDestroy {
114117
*/
115118
@Input() displayTitle = true;
116119

120+
/**
121+
* Defines whether to fetch search results during SSR execution
122+
*/
123+
@Input() renderOnServerSide: boolean;
124+
117125
scope$: BehaviorSubject<string> = new BehaviorSubject(undefined);
118126

119127
/**
@@ -194,6 +202,10 @@ export class BrowseByMetadataComponent implements OnInit, OnChanges, OnDestroy {
194202
* Observable determining if the loading animation needs to be shown
195203
*/
196204
loading$ = observableOf(true);
205+
/**
206+
* Whether this component should be rendered or not in SSR
207+
*/
208+
ssrRenderingDisabled = false;
197209

198210
public constructor(protected route: ActivatedRoute,
199211
protected browseService: BrowseService,
@@ -202,18 +214,23 @@ export class BrowseByMetadataComponent implements OnInit, OnChanges, OnDestroy {
202214
protected router: Router,
203215
@Inject(APP_CONFIG) public appConfig: AppConfig,
204216
public dsoNameService: DSONameService,
217+
@Inject(PLATFORM_ID) public platformId: any,
205218
) {
206219
this.fetchThumbnails = this.appConfig.browseBy.showThumbnails;
207220
this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
208221
id: BBM_PAGINATION_ID,
209222
currentPage: 1,
210223
pageSize: this.appConfig.browseBy.pageSize,
211224
});
225+
this.ssrRenderingDisabled = !this.renderOnServerSide && !environment.ssr.enableBrowseComponent && isPlatformServer(this.platformId);
212226
}
213227

214228

215229
ngOnInit(): void {
216-
230+
if (this.ssrRenderingDisabled) {
231+
this.loading$ = observableOf(false);
232+
return;
233+
}
217234
const sortConfig = new SortOptions('default', SortDirection.ASC);
218235
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig));
219236
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
@@ -336,7 +353,6 @@ export class BrowseByMetadataComponent implements OnInit, OnChanges, OnDestroy {
336353
this.paginationService.clearPagination(this.paginationConfig.id);
337354
}
338355

339-
340356
}
341357

342358
/**

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {
55
import { NO_ERRORS_SCHEMA } from '@angular/core';
66
import {
77
ComponentFixture,
8+
fakeAsync,
89
TestBed,
10+
tick,
911
waitForAsync,
1012
} from '@angular/core/testing';
1113
import {
@@ -23,6 +25,7 @@ import { BrowseService } from '../../core/browse/browse.service';
2325
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
2426
import { ItemDataService } from '../../core/data/item-data.service';
2527
import { PaginationService } from '../../core/pagination/pagination.service';
28+
import { BrowseEntry } from '../../core/shared/browse-entry.model';
2629
import { Community } from '../../core/shared/community.model';
2730
import { Item } from '../../core/shared/item.model';
2831
import { ThemedBrowseByComponent } from '../../shared/browse-by/themed-browse-by.component';
@@ -81,6 +84,7 @@ describe('BrowseByTitleComponent', () => {
8184

8285
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
8386
params: observableOf({}),
87+
queryParams: observableOf({}),
8488
data: observableOf({ metadata: 'title' }),
8589
});
8690

@@ -127,4 +131,35 @@ describe('BrowseByTitleComponent', () => {
127131
expect(result.payload.page).toEqual(mockItems);
128132
});
129133
});
134+
135+
describe('when rendered in SSR', () => {
136+
beforeEach(() => {
137+
comp.platformId = 'server';
138+
spyOn((comp as any).browseService, 'getBrowseItemsFor');
139+
fixture.detectChanges();
140+
});
141+
142+
it('should not call getBrowseItemsFor on init', (done) => {
143+
comp.ngOnInit();
144+
expect((comp as any).browseService.getBrowseItemsFor).not.toHaveBeenCalled();
145+
comp.loading$.subscribe((res) => {
146+
expect(res).toBeFalsy();
147+
done();
148+
});
149+
});
150+
});
151+
152+
describe('when rendered in CSR', () => {
153+
beforeEach(() => {
154+
comp.platformId = 'browser';
155+
fixture.detectChanges();
156+
spyOn((comp as any).browseService, 'getBrowseItemsFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
157+
});
158+
159+
it('should call getBrowseItemsFor on init', fakeAsync(() => {
160+
comp.ngOnInit();
161+
tick(100);
162+
expect((comp as any).browseService.getBrowseItemsFor).toHaveBeenCalled();
163+
}));
164+
});
130165
});

0 commit comments

Comments
 (0)