/** * 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 { APP_INITIALIZER, Inject, makeStateKey, Provider, TransferState, Type, } from '@angular/core'; import { DYNAMIC_FORM_CONTROL_MAP_FN } from '@ng-dynamic-forms/core'; import { select, Store, } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import isEqual from 'lodash/isEqual'; import { Observable } from 'rxjs'; import { distinctUntilChanged, find, } from 'rxjs/operators'; import { APP_CONFIG, APP_DATA_SERVICES_MAP, AppConfig, } from '../config/app-config.interface'; import { environment } from '../environments/environment'; import { AppState } from './app.reducer'; import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; import { CheckAuthenticationTokenAction } from './core/auth/auth.actions'; import { isAuthenticationBlocking } from './core/auth/selectors'; import { LAZY_DATA_SERVICES } from './core/data-services-map'; import { LocaleService } from './core/locale/locale.service'; import { HeadTagService } from './core/metadata/head-tag.service'; import { CorrelationIdService } from './correlation-id/correlation-id.service'; import { dsDynamicFormControlMapFn } from './shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-map-fn'; import { MenuService } from './shared/menu/menu.service'; import { ThemeService } from './shared/theme-support/theme.service'; import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider'; /** * Performs the initialization of the app. * * Should be extended to implement server- & browser-specific functionality. * Initialization steps shared between the server and brower implementations * can be included in this class. * * Note that the service cannot (indirectly) depend on injection tokens that are only available _after_ APP_INITIALIZER. * For example, NgbModal depends on ApplicationRef and can therefore not be used during initialization. */ export abstract class InitService { /** * The state transfer key to use for the NgRx store state * @protected */ protected static NGRX_STATE = makeStateKey('NGRX_STATE'); protected constructor( protected store: Store<AppState>, protected correlationIdService: CorrelationIdService, @Inject(APP_CONFIG) protected appConfig: AppConfig, protected translate: TranslateService, protected localeService: LocaleService, protected angulartics2DSpace: Angulartics2DSpace, protected headTagService: HeadTagService, protected breadcrumbsService: BreadcrumbsService, protected themeService: ThemeService, protected menuService: MenuService, ) { } /** * The initialization providers to use in `*AppModule` * - this concrete {@link InitService} * - {@link APP_CONFIG} with optional pre-initialization hook * - {@link APP_INITIALIZER} * <br> * Should only be called on concrete subclasses of InitService for the initialization hooks to work */ public static providers(): Provider[] { if (!InitService.isPrototypeOf(this)) { throw new Error( 'Initalization providers should only be generated from concrete subclasses of InitService', ); } return [ { provide: InitService, useClass: this as unknown as Type<InitService>, }, { provide: APP_CONFIG, useFactory: (transferState: TransferState) => { this.resolveAppConfig(transferState); return environment; }, deps: [ TransferState ], }, { provide: APP_INITIALIZER, useFactory: (initService: InitService) => initService.init(), deps: [ InitService ], multi: true, }, { provide: APP_DATA_SERVICES_MAP, useValue: LAZY_DATA_SERVICES, }, { provide: DYNAMIC_FORM_CONTROL_MAP_FN, useValue: dsDynamicFormControlMapFn, }, ]; } /** * Optional pre-initialization method to ensure that {@link APP_CONFIG} is fully resolved before {@link init} is called. * * For example, Router depends on APP_BASE_HREF, which in turn depends on APP_CONFIG. * In production mode, APP_CONFIG is resolved from the TransferState when the app is initialized. * If we want to use Router within APP_INITIALIZER, we have to make sure APP_BASE_HREF is resolved beforehand. * In this case that means that we must transfer the configuration from the SSR state during pre-initialization. * @protected */ protected static resolveAppConfig( transferState: TransferState, ): void { // overriden in subclasses if applicable } /** * Main initialization method. * @protected */ protected abstract init(): () => Promise<boolean>; // Common initialization steps /** * Dispatch a {@link CheckAuthenticationTokenAction} to start off the chain of * actions used to determine whether a user is already logged in. * @protected */ protected checkAuthenticationToken(): void { this.store.dispatch(new CheckAuthenticationTokenAction()); } /** * Initialize the correlation ID (from cookie, NgRx store or random) * @protected */ protected initCorrelationId(): void { this.correlationIdService.initCorrelationId(); } /** * Make sure the {@link environment} matches {@link APP_CONFIG} and print * some information about it to the console * @protected */ protected checkEnvironment(): void { if (!isEqual(environment, this.appConfig)) { throw new Error('environment does not match app config!'); } if (environment.debug) { console.info(environment); } } /** * Initialize internationalization services * - Specify the active languages * - Set the current locale * @protected */ protected initI18n(): void { // Load all the languages that are defined as active from the config file this.translate.addLangs( environment.languages .filter((LangConfig) => LangConfig.active === true) .map((a) => a.code), ); // Load the default language from the config file // translate.setDefaultLang(environment.defaultLanguage); this.localeService.setCurrentLanguageCode(); } /** * Initialize Angulartics * @protected */ protected initAngulartics(): void { this.angulartics2DSpace.startTracking(); } /** * Start route-listening subscriptions * - {@link HeadTagService.listenForRouteChange} * - {@link BreadcrumbsService.listenForRouteChanges} * - {@link ThemeService.listenForRouteChanges} * @protected */ protected initRouteListeners(): void { this.headTagService.listenForRouteChange(); this.breadcrumbsService.listenForRouteChanges(); this.themeService.listenForRouteChanges(); this.menuService.listenForRouteChanges(); } /** * Emits once authentication is ready (no longer blocking) * @protected */ protected authenticationReady$(): Observable<boolean> { return this.store.pipe( select(isAuthenticationBlocking), distinctUntilChanged(), find((b: boolean) => b === false), ); } }