93219: Support Router in InitService

For Router to work properly, APP_BASE_HREF must be resolved _before_ the APP_INITIALIZER factory is called (otherwise Angular will attempt to initialize APP_BASE_HREF too soon)
To fix this we add a pre-initialization hook to APP_CONFIG so BrowserInitService can resolve it before APP_INITIALIZER
This commit is contained in:
Yura Bondarenko
2022-07-19 17:58:19 +02:00
parent 39c2aa85ec
commit 372cddfd5e
8 changed files with 164 additions and 64 deletions

View File

@@ -6,30 +6,18 @@
* 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';
import { APP_INITIALIZER, Provider, Type } from '@angular/core';
import { TransferState } from '@angular/platform-browser';
import { APP_CONFIG } from '../config/app-config.interface';
import { environment } from '../environments/environment';
import { AppState } from './app.reducer';
/**
* 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 }
* ```
* Should be extended to implement server- & browser-specific functionality.
*/
export abstract class InitService {
protected constructor(
@@ -40,14 +28,63 @@ export abstract class InitService {
}
/**
* 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)
* 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
*/
abstract init(): () => Promise<boolean>;
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,
},
];
}
/**
* 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
protected checkAuthenticationToken(): void {
this.store.dispatch(new CheckAuthenticationTokenAction());