refactor: initialize config via APP_INITIALIZER

Closes #181
This commit is contained in:
Jovan Krunić
2022-02-09 20:43:08 +01:00
parent 411e0970b8
commit bde0df219c
9 changed files with 37 additions and 72 deletions

View File

@@ -17,8 +17,6 @@ import {Router} from '@angular/router';
import {App, URLOpenListenerEvent} from '@capacitor/app'; import {App, URLOpenListenerEvent} from '@capacitor/app';
import {SplashScreen} from '@capacitor/splash-screen'; import {SplashScreen} from '@capacitor/splash-screen';
import {Platform, ToastController} from '@ionic/angular'; import {Platform, ToastController} from '@ionic/angular';
import {NGXLogger} from 'ngx-logger';
import {ConfigProvider} from './modules/config/config.provider';
import {SettingsProvider} from './modules/settings/settings.provider'; import {SettingsProvider} from './modules/settings/settings.provider';
import {PAIAAuthService} from './modules/auth/paia/paia-auth.service'; import {PAIAAuthService} from './modules/auth/paia/paia-auth.service';
import {DefaultAuthService} from './modules/auth/default-auth.service'; import {DefaultAuthService} from './modules/auth/default-auth.service';
@@ -52,8 +50,6 @@ export class AppComponent implements AfterContentInit {
* *
* @param platform TODO * @param platform TODO
* @param settingsProvider TODO * @param settingsProvider TODO
* @param configProvider TODO
* @param logger An angular logger
* @param router The angular router * @param router The angular router
* @param zone The angular zone * @param zone The angular zone
* @param defaultAuth Auth Service * @param defaultAuth Auth Service
@@ -65,8 +61,6 @@ export class AppComponent implements AfterContentInit {
constructor( constructor(
private readonly platform: Platform, private readonly platform: Platform,
private readonly settingsProvider: SettingsProvider, private readonly settingsProvider: SettingsProvider,
private readonly configProvider: ConfigProvider,
private readonly logger: NGXLogger,
private readonly router: Router, private readonly router: Router,
private readonly zone: NgZone, private readonly zone: NgZone,
private readonly defaultAuth: DefaultAuthService, private readonly defaultAuth: DefaultAuthService,
@@ -102,19 +96,6 @@ export class AppComponent implements AfterContentInit {
await this.paiaAuth.init(); await this.paiaAuth.init();
await SplashScreen.hide(); await SplashScreen.hide();
// initialise the configProvider
try {
await this.configProvider.init();
} catch (error) {
if (
typeof error.name !== 'undefined' &&
error.name === 'ConfigInitError'
) {
// TODO: Issue #43 handle initialisation error and inform user
}
this.logger.error(error);
}
// set order of categories in settings // set order of categories in settings
this.settingsProvider.setCategoriesOrder([ this.settingsProvider.setCategoriesOrder([
'profile', 'profile',

View File

@@ -65,18 +65,21 @@ import {AuthModule} from './modules/auth/auth.module';
import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.service'; import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.service';
import {BackgroundModule} from './modules/background/background.module'; import {BackgroundModule} from './modules/background/background.module';
import {LibraryModule} from './modules/library/library.module'; import {LibraryModule} from './modules/library/library.module';
import {StorageProvider} from './modules/storage/storage.provider';
registerLocaleData(localeDe); registerLocaleData(localeDe);
/** /**
* Initializes settings from Config before other components * Initializes data needed on startup
* *
* @param storageProvider provider of the saved data (using framework's storage)
* @param logger TODO * @param logger TODO
* @param settingsProvider provider of settings (e.g. language that has been set) * @param settingsProvider provider of settings (e.g. language that has been set)
* @param configProvider TODO * @param configProvider TODO
* @param translateService TODO * @param translateService TODO
*/ */
export function initSettingsFactory( export function initializerFactory(
storageProvider: StorageProvider,
logger: NGXLogger, logger: NGXLogger,
settingsProvider: SettingsProvider, settingsProvider: SettingsProvider,
configProvider: ConfigProvider, configProvider: ConfigProvider,
@@ -84,10 +87,12 @@ export function initSettingsFactory(
) { ) {
return async () => { return async () => {
initLogger(logger); initLogger(logger);
await storageProvider.init();
await configProvider.init();
await settingsProvider.init(); await settingsProvider.init();
try { try {
// set language from settings
if (configProvider.firstSession) { if (configProvider.firstSession) {
// set language from browser
await settingsProvider.setSettingValue( await settingsProvider.setSettingValue(
'profile', 'profile',
'language', 'language',
@@ -184,13 +189,14 @@ export function createTranslateLoader(http: HttpClient) {
provide: APP_INITIALIZER, provide: APP_INITIALIZER,
multi: true, multi: true,
deps: [ deps: [
StorageProvider,
NGXLogger, NGXLogger,
SettingsProvider, SettingsProvider,
ConfigProvider, ConfigProvider,
TranslateService, TranslateService,
ScheduleSyncService, ScheduleSyncService,
], ],
useFactory: initSettingsFactory, useFactory: initializerFactory,
}, },
], ],
}) })

View File

@@ -23,7 +23,7 @@ import {ConfigProvider} from '../../config/config.provider';
styleUrls: ['about-page.scss'], styleUrls: ['about-page.scss'],
}) })
export class AboutPageComponent implements OnInit { export class AboutPageComponent implements OnInit {
content: Promise<SCAboutPage>; content: SCAboutPage;
constructor( constructor(
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
@@ -33,12 +33,11 @@ export class AboutPageComponent implements OnInit {
async ngOnInit() { async ngOnInit() {
const route = this.route.snapshot.url.map(it => it.path).join('/'); const route = this.route.snapshot.url.map(it => it.path).join('/');
this.content = new Promise(resolve => { this.content =
this.configProvider (
.getValue('aboutPages') this.configProvider.getValue(
.then(value => 'aboutPages',
resolve((value as SCAppConfiguration['aboutPages'])[route] ?? {}), ) as SCAppConfiguration['aboutPages']
); )[route] ?? {};
});
} }
} }

View File

@@ -19,7 +19,7 @@
<ion-menu-button></ion-menu-button> <ion-menu-button></ion-menu-button>
<ion-back-button></ion-back-button> <ion-back-button></ion-back-button>
</ion-buttons> </ion-buttons>
<ion-title *ngIf="content | async as content; else titleLoading">{{ <ion-title *ngIf="content; else titleLoading">{{
'title' | translateSimple: content 'title' | translateSimple: content
}}</ion-title> }}</ion-title>
<ng-template #titleLoading> <ng-template #titleLoading>
@@ -29,7 +29,7 @@
</ng-template> </ng-template>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content *ngIf="content | async as content"> <ion-content *ngIf="content">
<about-page-content <about-page-content
*ngFor="let element of content.content" *ngFor="let element of content.content"
[content]="element" [content]="element"

View File

@@ -107,7 +107,6 @@ describe('ConfigProvider', () => {
expect(storageProviderSpy.has).toHaveBeenCalled(); expect(storageProviderSpy.has).toHaveBeenCalled();
expect(storageProviderSpy.get).toHaveBeenCalledTimes(0); expect(storageProviderSpy.get).toHaveBeenCalledTimes(0);
expect(configProvider.client.handshake).toHaveBeenCalled(); expect(configProvider.client.handshake).toHaveBeenCalled();
expect(configProvider.initialised).toBe(true);
expect(await configProvider.getValue('name')).toEqual( expect(await configProvider.getValue('name')).toEqual(
sampleIndexResponse.app.name, sampleIndexResponse.app.name,
); );
@@ -129,7 +128,6 @@ describe('ConfigProvider', () => {
expect(error).toEqual(new ConfigFetchError()); expect(error).toEqual(new ConfigFetchError());
expect(storageProviderSpy.has).toHaveBeenCalled(); expect(storageProviderSpy.has).toHaveBeenCalled();
expect(storageProviderSpy.get).toHaveBeenCalled(); expect(storageProviderSpy.get).toHaveBeenCalled();
expect(configProvider.initialised).toBe(true);
expect(await configProvider.getValue('name')).toEqual( expect(await configProvider.getValue('name')).toEqual(
sampleIndexResponse.app.name, sampleIndexResponse.app.name,
); );

View File

@@ -50,21 +50,16 @@ export class ConfigProvider {
*/ */
config: SCIndexResponse; config: SCIndexResponse;
/**
* First session indicator
*/
firstSession = true;
/**
* Initialised status flag of config provider
*/
initialised = false;
/** /**
* Version of the @openstapps/core package that app is using * Version of the @openstapps/core package that app is using
*/ */
scVersion = packageJson.dependencies['@openstapps/core']; scVersion = packageJson.dependencies['@openstapps/core'];
/**
* First session indicator (config not found in storage)
*/
firstSession = true;
/** /**
* Constructor, initialise api client * Constructor, initialise api client
* *
@@ -100,17 +95,7 @@ export class ConfigProvider {
* *
* @param attribute requested attribute from app configuration * @param attribute requested attribute from app configuration
*/ */
public async getValue(attribute: keyof SCAppConfiguration) { public getValue(attribute: keyof SCAppConfiguration) {
if (!this.initialised) {
try {
await this.init();
} catch (error) {
// don't throw ConfigFetchError if saved config is available
if (error.name === 'ConfigFetchError' && !this.initialised) {
throw error;
}
}
}
if (typeof this.config.app[attribute] !== 'undefined') { if (typeof this.config.app[attribute] !== 'undefined') {
return this.config.app[attribute]; return this.config.app[attribute];
} }
@@ -138,12 +123,10 @@ export class ConfigProvider {
async init(): Promise<void> { async init(): Promise<void> {
let loadError; let loadError;
let fetchError; let fetchError;
this.initialised = false;
// load saved configuration // load saved configuration
try { try {
this.config = await this.loadLocal(); this.config = await this.loadLocal();
this.firstSession = false; this.firstSession = false;
this.initialised = true;
this.logger.log(`initialised configuration from storage`); this.logger.log(`initialised configuration from storage`);
if (this.config.backend.SCVersion !== this.scVersion) { if (this.config.backend.SCVersion !== this.scVersion) {
loadError = new WrongConfigVersionInStorage( loadError = new WrongConfigVersionInStorage(
@@ -159,7 +142,6 @@ export class ConfigProvider {
try { try {
const fetchedConfig: SCIndexResponse = await this.fetch(); const fetchedConfig: SCIndexResponse = await this.fetch();
await this.set(fetchedConfig); await this.set(fetchedConfig);
this.initialised = true;
this.logger.log(`initialised configuration from remote`); this.logger.log(`initialised configuration from remote`);
} catch (error) { } catch (error) {
fetchError = error; fetchError = error;
@@ -169,7 +151,7 @@ export class ConfigProvider {
throw new ConfigInitError(); throw new ConfigInitError();
} }
if (typeof loadError !== 'undefined') { if (typeof loadError !== 'undefined') {
throw loadError; this.logger.warn(loadError);
} }
if (typeof fetchError !== 'undefined') { if (typeof fetchError !== 'undefined') {
throw fetchError; throw fetchError;
@@ -182,7 +164,6 @@ export class ConfigProvider {
* @throws SavedConfigNotAvailable if no configuration could be loaded * @throws SavedConfigNotAvailable if no configuration could be loaded
*/ */
async loadLocal(): Promise<SCIndexResponse> { async loadLocal(): Promise<SCIndexResponse> {
await this.storageProvider.init();
// get local configuration // get local configuration
if (await this.storageProvider.has(STORAGE_KEY_CONFIG)) { if (await this.storageProvider.has(STORAGE_KEY_CONFIG)) {
return this.storageProvider.get<SCIndexResponse>(STORAGE_KEY_CONFIG); return this.storageProvider.get<SCIndexResponse>(STORAGE_KEY_CONFIG);

View File

@@ -13,7 +13,6 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {APP_INITIALIZER, NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms'; import {FormsModule} from '@angular/forms';
import {RouterModule, Routes} from '@angular/router'; import {RouterModule, Routes} from '@angular/router';
import {LeafletModule} from '@asymmetrik/ngx-leaflet'; import {LeafletModule} from '@asymmetrik/ngx-leaflet';
@@ -34,6 +33,7 @@ import {MapPageComponent} from './page/map-page.component';
import {MapListModalComponent} from './page/modals/map-list-modal.component'; import {MapListModalComponent} from './page/modals/map-list-modal.component';
import {MapSingleModalComponent} from './page/modals/map-single-modal.component'; import {MapSingleModalComponent} from './page/modals/map-single-modal.component';
import {MapItemComponent} from './item/map-item.component'; import {MapItemComponent} from './item/map-item.component';
import {NgModule} from '@angular/core';
/** /**
* Initializes the default area to show in advance (before components are initialized) * Initializes the default area to show in advance (before components are initialized)
@@ -86,12 +86,6 @@ const mapRoutes: Routes = [
DataProvider, DataProvider,
DataFacetsProvider, DataFacetsProvider,
StAppsWebHttpClient, StAppsWebHttpClient,
{
provide: APP_INITIALIZER,
multi: true,
deps: [ConfigProvider, MapProvider],
useFactory: initMapConfigFactory,
},
], ],
}) })
export class MapModule {} export class MapModule {}

View File

@@ -26,6 +26,7 @@ import {divIcon, geoJSON, icon, LatLng, Map, marker, Marker} from 'leaflet';
import {DataProvider} from '../data/data.provider'; import {DataProvider} from '../data/data.provider';
import {MapPosition, PositionService} from './position.service'; import {MapPosition, PositionService} from './position.service';
import {hasValidLocation} from '../data/types/place/place-types'; import {hasValidLocation} from '../data/types/place/place-types';
import {ConfigProvider} from '../config/config.provider';
/** /**
* Provides methods for presenting the map * Provides methods for presenting the map
@@ -107,7 +108,12 @@ export class MapProvider {
constructor( constructor(
private dataProvider: DataProvider, private dataProvider: DataProvider,
private positionService: PositionService, private positionService: PositionService,
) {} private configProvider: ConfigProvider,
) {
this.defaultPolygon = this.configProvider.getValue(
'campusPolygon',
) as Polygon;
}
/** /**
* Provide the specific place by its UID * Provide the specific place by its UID
@@ -128,7 +134,7 @@ export class MapProvider {
/** /**
* Provide places (buildings and canteens) const result = await this.dataProvider.search(query); * Provide places (buildings and canteens) const result = await this.dataProvider.search(query);
* *
* @param contextFilter Additional contextual filter (e.g. from the context menu) * @param contextFilter Additional contextual filter (e.g. from the context menu)
* @param queryText Query (text) of the search query * @param queryText Query (text) of the search query

View File

@@ -333,9 +333,9 @@ export class SettingsProvider {
*/ */
public async init(): Promise<void> { public async init(): Promise<void> {
try { try {
const settings: SCSetting[] = (await this.configProvider.getValue( const settings: SCSetting[] = this.configProvider.getValue(
'settings', 'settings',
)) as SCSetting[]; ) as SCSetting[];
for (const setting of settings) this.addSetting(setting); for (const setting of settings) this.addSetting(setting);
for (const category of Object.keys(this.settingsCache)) { for (const category of Object.keys(this.settingsCache)) {