From 39c2aa85eca5c94060bb4618c371a896f8b9e1de Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Mon, 18 Jul 2022 15:55:30 +0200 Subject: [PATCH] 93219: Move APP_INITIALIZER logic into InitService --- src/app/app.module.ts | 25 ++++------ src/app/init.service.ts | 63 +++++++++++++++++++++++++ src/modules/app/browser-app.module.ts | 38 +++------------ src/modules/app/browser-init.service.ts | 54 +++++++++++++++++++++ src/modules/app/server-app.module.ts | 26 ++-------- src/modules/app/server-init.service.ts | 46 ++++++++++++++++++ 6 files changed, 183 insertions(+), 69 deletions(-) create mode 100644 src/app/init.service.ts create mode 100644 src/modules/app/browser-init.service.ts create mode 100644 src/modules/app/server-init.service.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a1db89b60d..e3f5c46adb 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -7,13 +7,9 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { EffectsModule } from '@ngrx/effects'; import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store'; -import { MetaReducer, Store, StoreModule, USER_PROVIDED_META_REDUCERS } from '@ngrx/store'; +import { MetaReducer, StoreModule, USER_PROVIDED_META_REDUCERS } from '@ngrx/store'; import { StoreDevtoolsModule } from '@ngrx/store-devtools'; -import { - DYNAMIC_ERROR_MESSAGES_MATCHER, - DYNAMIC_MATCHER_PROVIDERS, - DynamicErrorMessagesMatcher -} from '@ng-dynamic-forms/core'; +import { DYNAMIC_ERROR_MESSAGES_MATCHER, DYNAMIC_MATCHER_PROVIDERS, DynamicErrorMessagesMatcher } from '@ng-dynamic-forms/core'; import { TranslateModule } from '@ngx-translate/core'; import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to'; import { AppRoutingModule } from './app-routing.module'; @@ -21,7 +17,6 @@ import { AppComponent } from './app.component'; import { appEffects } from './app.effects'; import { appMetaReducers, debugMetaReducers } from './app.metareducers'; import { appReducers, AppState, storeModuleConfig } from './app.reducer'; -import { CheckAuthenticationTokenAction } from './core/auth/auth.actions'; import { CoreModule } from './core/core.module'; import { ClientCookieService } from './core/services/client-cookie.service'; import { NavbarModule } from './navbar/navbar.module'; @@ -36,6 +31,7 @@ import { EagerThemesModule } from '../themes/eager-themes.module'; import { APP_CONFIG, AppConfig } from '../config/app-config.interface'; import { RootModule } from './root.module'; +import { InitService } from './init.service'; export function getConfig() { return environment; @@ -82,6 +78,12 @@ IMPORTS.push( ); const PROVIDERS = [ + { + provide: APP_INITIALIZER, + useFactory: (initService: InitService) => initService.init(), + deps: [ InitService ], + multi: true, + }, { provide: APP_CONFIG, useFactory: getConfig @@ -101,15 +103,6 @@ const PROVIDERS = [ useClass: DSpaceRouterStateSerializer }, ClientCookieService, - // Check the authentication token when the app initializes - { - provide: APP_INITIALIZER, - useFactory: (store: Store,) => { - return () => store.dispatch(new CheckAuthenticationTokenAction()); - }, - deps: [Store], - multi: true - }, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/init.service.ts b/src/app/init.service.ts new file mode 100644 index 0000000000..c5bd6dddb0 --- /dev/null +++ b/src/app/init.service.ts @@ -0,0 +1,63 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +import { Store } from '@ngrx/store'; +import { AppState } from './app.reducer'; +import { CheckAuthenticationTokenAction } from './core/auth/auth.actions'; +import { CorrelationIdService } from './correlation-id/correlation-id.service'; +import { DSpaceTransferState } from '../modules/transfer-state/dspace-transfer-state.service'; + +/** + * Performs the initialization of the app. + * + * Should have distinct extensions for server & browser for the specifics. + * Should be provided in AppModule as follows + * ``` + * { + * provide: APP_INITIALIZER + * useFactory: (initService: InitService) => initService.init(), + * deps: [ InitService ], + * multi: true, + * } + * ``` + * + * In order to be injected in the common APP_INITIALIZER, + * concrete subclasses should be provided in their respective app modules as + * ``` + * { provide: InitService, useClass: SpecificInitService } + * ``` + */ +export abstract class InitService { + protected constructor( + protected store: Store, + protected correlationIdService: CorrelationIdService, + protected dspaceTransferState: DSpaceTransferState, + ) { + } + + /** + * Main initialization method, to be used as the APP_INITIALIZER factory. + * + * Note that the body of this method and the callback it returns are called + * at different times. + * This is important to take into account when other providers depend on the + * initialization logic (e.g. APP_BASE_HREF) + */ + abstract init(): () => Promise; + + protected checkAuthenticationToken(): void { + this.store.dispatch(new CheckAuthenticationTokenAction()); + } + + protected initCorrelationId(): void { + this.correlationIdService.initCorrelationId(); + } + + protected async transferAppState(): Promise { + return this.dspaceTransferState.transfer(); + } +} diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index 20c68898ae..e50018b51a 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -1,5 +1,5 @@ import { HttpClient, HttpClientModule } from '@angular/common/http'; -import { APP_INITIALIZER, NgModule } from '@angular/core'; +import { NgModule } from '@angular/core'; import { BrowserModule, makeStateKey, TransferState } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { REQUEST } from '@nguniversal/express-engine/tokens'; @@ -13,7 +13,6 @@ import { AppComponent } from '../../app/app.component'; import { AppModule } from '../../app/app.module'; import { DSpaceBrowserTransferStateModule } from '../transfer-state/dspace-browser-transfer-state.module'; -import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service'; import { ClientCookieService } from '../../app/core/services/client-cookie.service'; import { CookieService } from '../../app/core/services/cookie.service'; import { AuthService } from '../../app/core/auth/auth.service'; @@ -23,21 +22,13 @@ import { StatisticsModule } from '../../app/statistics/statistics.module'; import { BrowserKlaroService } from '../../app/shared/cookies/browser-klaro.service'; import { KlaroService } from '../../app/shared/cookies/klaro.service'; import { HardRedirectService } from '../../app/core/services/hard-redirect.service'; -import { - BrowserHardRedirectService, - locationProvider, - LocationToken -} from '../../app/core/services/browser-hard-redirect.service'; +import { BrowserHardRedirectService, locationProvider, LocationToken } from '../../app/core/services/browser-hard-redirect.service'; import { LocaleService } from '../../app/core/locale/locale.service'; import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service'; import { AuthRequestService } from '../../app/core/auth/auth-request.service'; import { BrowserAuthRequestService } from '../../app/core/auth/browser-auth-request.service'; -import { AppConfig, APP_CONFIG_STATE } from '../../config/app-config.interface'; -import { DefaultAppConfig } from '../../config/default-app-config'; -import { extendEnvironmentWithAppConfig } from '../../config/config.util'; -import { CorrelationIdService } from '../../app/correlation-id/correlation-id.service'; - -import { environment } from '../../environments/environment'; +import { InitService } from 'src/app/init.service'; +import { BrowserInitService } from './browser-init.service'; export const REQ_KEY = makeStateKey('req'); @@ -73,25 +64,8 @@ export function getRequest(transferState: TransferState): any { ], providers: [ { - provide: APP_INITIALIZER, - useFactory: ( - transferState: TransferState, - dspaceTransferState: DSpaceTransferState, - correlationIdService: CorrelationIdService - ) => { - if (transferState.hasKey(APP_CONFIG_STATE)) { - const appConfig = transferState.get(APP_CONFIG_STATE, new DefaultAppConfig()); - // extend environment with app config for browser - extendEnvironmentWithAppConfig(environment, appConfig); - } - return () => - dspaceTransferState.transfer().then((b: boolean) => { - correlationIdService.initCorrelationId(); - return b; - }); - }, - deps: [TransferState, DSpaceTransferState, CorrelationIdService], - multi: true + provide: InitService, + useClass: BrowserInitService, }, { provide: REQUEST, diff --git a/src/modules/app/browser-init.service.ts b/src/modules/app/browser-init.service.ts new file mode 100644 index 0000000000..1b050af82a --- /dev/null +++ b/src/modules/app/browser-init.service.ts @@ -0,0 +1,54 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +import { InitService } from '../../app/init.service'; +import { Store } from '@ngrx/store'; +import { AppState } from '../../app/app.reducer'; +import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service'; +import { TransferState } from '@angular/platform-browser'; +import { APP_CONFIG_STATE, AppConfig } from '../../config/app-config.interface'; +import { DefaultAppConfig } from '../../config/default-app-config'; +import { extendEnvironmentWithAppConfig } from '../../config/config.util'; +import { environment } from '../../environments/environment'; +import { CorrelationIdService } from '../../app/correlation-id/correlation-id.service'; +import { Injectable } from '@angular/core'; + +/** + * Performs client-side initialization. + */ +@Injectable() +export class BrowserInitService extends InitService { + constructor( + protected store: Store, + protected correlationIdService: CorrelationIdService, + protected transferState: TransferState, + protected dspaceTransferState: DSpaceTransferState, + ) { + super(store, correlationIdService, dspaceTransferState); + } + + public init(): () => Promise { + // this method must be called before the callback because APP_BASE_HREF depends on it + this.loadAppConfigFromSSR(); + + return async () => { + await this.transferAppState(); + this.checkAuthenticationToken(); + this.initCorrelationId(); + + return true; + }; + } + + private loadAppConfigFromSSR(): void { + if (this.transferState.hasKey(APP_CONFIG_STATE)) { + const appConfig = this.transferState.get(APP_CONFIG_STATE, new DefaultAppConfig()); + // extend environment with app config for browser + extendEnvironmentWithAppConfig(environment, appConfig); + } + } +} diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index 4f8569962c..f61712ccb0 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -1,9 +1,8 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http'; -import { APP_INITIALIZER, NgModule } from '@angular/core'; +import { NgModule } from '@angular/core'; import { BrowserModule, TransferState } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ServerModule } from '@angular/platform-server'; -import { RouterModule } from '@angular/router'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; @@ -14,7 +13,6 @@ import { AppComponent } from '../../app/app.component'; import { AppModule } from '../../app/app.module'; import { DSpaceServerTransferStateModule } from '../transfer-state/dspace-server-transfer-state.module'; -import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service'; import { TranslateServerLoader } from '../../ngx-translate-loaders/translate-server.loader'; import { CookieService } from '../../app/core/services/cookie.service'; import { ServerCookieService } from '../../app/core/services/server-cookie.service'; @@ -32,10 +30,8 @@ import { ServerHardRedirectService } from '../../app/core/services/server-hard-r import { Angulartics2Mock } from '../../app/shared/mocks/angulartics2.service.mock'; import { AuthRequestService } from '../../app/core/auth/auth-request.service'; import { ServerAuthRequestService } from '../../app/core/auth/server-auth-request.service'; -import { CorrelationIdService } from '../../app/correlation-id/correlation-id.service'; -import { AppConfig, APP_CONFIG_STATE } from '../../config/app-config.interface'; - -import { environment } from '../../environments/environment'; +import { InitService } from '../../app/init.service'; +import { ServerInitService } from './server-init.service'; export function createTranslateLoader(transferState: TransferState) { return new TranslateServerLoader(transferState, 'dist/server/assets/i18n/', '.json5'); @@ -60,21 +56,9 @@ export function createTranslateLoader(transferState: TransferState) { AppModule ], providers: [ - // Initialize app config and extend environment { - provide: APP_INITIALIZER, - useFactory: ( - transferState: TransferState, - dspaceTransferState: DSpaceTransferState, - correlationIdService: CorrelationIdService, - ) => { - transferState.set(APP_CONFIG_STATE, environment as AppConfig); - dspaceTransferState.transfer(); - correlationIdService.initCorrelationId(); - return () => true; - }, - deps: [TransferState, DSpaceTransferState, CorrelationIdService], - multi: true + provide: InitService, + useClass: ServerInitService, }, { provide: Angulartics2, diff --git a/src/modules/app/server-init.service.ts b/src/modules/app/server-init.service.ts new file mode 100644 index 0000000000..d5814f10f3 --- /dev/null +++ b/src/modules/app/server-init.service.ts @@ -0,0 +1,46 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +import { InitService } from '../../app/init.service'; +import { Store } from '@ngrx/store'; +import { AppState } from '../../app/app.reducer'; +import { TransferState } from '@angular/platform-browser'; +import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service'; +import { CorrelationIdService } from '../../app/correlation-id/correlation-id.service'; +import { APP_CONFIG_STATE, AppConfig } from '../../config/app-config.interface'; +import { environment } from '../../environments/environment'; +import { Injectable } from '@angular/core'; + +/** + * Performs server-side initialization. + */ +@Injectable() +export class ServerInitService extends InitService { + constructor( + protected store: Store, + protected correlationIdService: CorrelationIdService, + protected transferState: TransferState, + protected dspaceTransferState: DSpaceTransferState, + ) { + super(store, correlationIdService, dspaceTransferState); + } + + public init(): () => Promise { + return async () => { + this.checkAuthenticationToken(); + this.saveAppConfigForCSR(); + this.transferAppState(); // todo: SSR breaks if we await this (why?) + this.initCorrelationId(); + + return true; + }; + } + + private saveAppConfigForCSR(): void { + this.transferState.set(APP_CONFIG_STATE, environment as AppConfig); + } +}