UNPKG

ngx-runtime-initializer

Version:

Angular package for loading runtime configuration, managing app settings, and injecting services with post-initialization callbacks, compatible with standalone Angular 19+ applications.

297 lines (288 loc) 11.1 kB
import * as i0 from '@angular/core'; import { signal, Injectable, inject, provideAppInitializer } from '@angular/core'; import { Router } from '@angular/router'; import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { firstValueFrom, timeout, catchError, throwError, of } from 'rxjs'; /** * Implementation of the runtime application configuration. * * Holds core configuration values (API URL, debug mode, request timeout) * and the current status of the application. * * Can be initialized partially using a `Partial<RuntimeConfig>` object, * with optional override for the `status` flag. * * @example * // Default initialization * const config = new AppConfig(); * * // Initialize with custom API URL and enable debug * const config = new AppConfig({ * coreConfig: { apiURL: 'https://api.example.com', debug: true, requestTimeout: 20000 } * }, true); */ class AppConfig { /** * Core configuration object containing API URL, debug flag, and request timeout. */ coreConfig = { apiURL: '__BACKEND__', debug: false, requestTimeout: 30000, }; /** * Status of the application (true = up/active, false = down/maintenance). */ status = false; /** * Creates a new AppConfig instance. * * @param init - Partial configuration to override default values. * @param status - Optional status override (default is `false`). */ constructor(init, status = false) { Object.assign(this, init); this.status = status; } } /** * Service for managing the application's runtime configuration. * * Provides reactive access to the configuration using Angular signals, * allows updating configuration at runtime, and exposes convenience * methods to retrieve configuration values or application status. * * Additionally, it sets global variables `$CORE_API` and `$DEBUG` * when the configuration is updated. */ class AppConfigService { /** * Internal writable signal holding the current application configuration. */ config = signal(new AppConfig()); /** * Updates the application configuration. * * This will also update the global variables: * - `$CORE_API` with `config.coreConfig.apiURL` * - `$DEBUG` with `config.coreConfig.debug` * * @param config - The new configuration to set. */ setConfig(config) { globalThis.$CORE_API = config.coreConfig.apiURL; globalThis.$DEBUG = config.coreConfig.debug; this.config.set(config); } /** * Returns the current status of the application. * * @returns `true` if the app is up/active, `false` if it is down/maintenance. */ getStatus() { return this.config().status; } /** * Returns the reactive `WritableSignal` holding the configuration. * * Consumers can subscribe to this signal for reactive updates. * * @returns The `WritableSignal<AppConfig>` instance. */ getConfigSignal() { return this.config; } /** * Returns the current configuration snapshot. * * @returns The current `AppConfig` object. */ getConfig() { return this.config(); } /** * Retrieves a specific property from the current configuration. * * @typeParam T - Expected type of the property. * @param key - The key of the property in `AppConfig`. * @returns The value of the property if it exists, otherwise `undefined`. * * @example * const apiUrl = service.getConfigProperty<string>('coreConfig').apiURL; */ getConfigProperty(key) { return this.config()[key]; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: AppConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: AppConfigService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: AppConfigService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); /** * Factory function to create a route guard that checks the application status. * * The guard compares the current application status from {@link AppConfigService} * with the expected `shouldBeOk` value. If the actual status does not match, * the user is redirected to the provided `redirectUrl`. * * @param shouldBeOk - Expected status: * - `true`: route is accessible only if the app is up/active * - `false`: route is accessible only if the app is down/maintenance * @param redirectUrl - URL to redirect to when the status does not match `shouldBeOk` * @returns A `CanActivateFn` that enforces the status check. * * @example * // Only allow access when the app is up * const guard = createStatusGuard(true, '/maintenance'); * * // Only allow access when the app is down * const guard = createStatusGuard(false, '/'); */ function createStatusGuard(shouldBeOk, redirectUrl) { return (route, state) => { const router = inject(Router); const isStatusOK = inject(AppConfigService).getStatus(); if (isStatusOK !== shouldBeOk) { router.navigate([redirectUrl], { replaceUrl: true }); return false; } return true; }; } /** * Predefined guard that only allows access when the application is UP. * Redirects to `/maintenance` if the application is down. */ const InMaintenanceGuard = createStatusGuard(true, '/maintenance'); /** * Predefined guard that only allows access when the application is DOWN. * Redirects to `/` if the application is up. */ const MaintenanceGuard = createStatusGuard(false, '/'); /** * Options for configuring the runtime initializer. */ class RuntimeInitializerOptions { /** URL of the runtime configuration JSON */ configUrl = 'config.json'; /** Array of Angular service classes to inject and pass to callbacks */ servicesToInject = []; /** * Optional async callback executed after successful configuration load. * Receives the loaded `AppConfig` and an object mapping injected services by class name. */ postInit = async () => { }; /** * Optional callback executed if initialization fails. * Receives the error and injected services object. */ handleInitializationFailure = () => { }; /** * Constructor for fast initialization or custom values. * @param configUrl - URL of the config JSON * @param servicesToInject - Array of Angular services to inject * @param postInit - Callback after successful initialization * @param handleInitializationFailure - Callback on initialization failure */ constructor(configUrl, servicesToInject, postInit, handleInitializationFailure) { if (configUrl) this.configUrl = configUrl; if (servicesToInject) this.servicesToInject = servicesToInject; if (postInit) this.postInit = postInit; if (handleInitializationFailure) this.handleInitializationFailure = handleInitializationFailure; } } /** * Provides an Angular application initializer that loads runtime configuration from a JSON file * and optionally injects additional services. * * @param options - Configuration options for the runtime initializer. * * @returns A provider for Angular's `APP_INITIALIZER`. * * @example * // Basic usage: load config from default 'config.json' * provideRuntimeInitializer({}); * * @example * // With custom services and post-init callback * provideRuntimeInitializer({ * configUrl: '/assets/runtime-config.json', * servicesToInject: [MyService1, MyService2], * postInit: async (config, services) => { * console.log('Config loaded:', config); * services.MyService1?.initialize(); * }, * handleInitializationFailure: (error, services) => { * console.warn('Failed to initialize runtime config:', error); * } * }); */ function provideRuntimeInitializer({ configUrl = 'config.json', servicesToInject = [], postInit = async () => { }, handleInitializationFailure = () => { }, }) { return provideAppInitializer(async () => { const http = inject(HttpClient); const appService = inject(AppConfigService); const injectedServices = {}; servicesToInject.forEach((cls) => { try { injectedServices[cls.name.replace('_', '')] = inject(cls, { optional: true }); } catch { injectedServices[cls.name.replace('_', '')] = undefined; } }); try { const runtimeConfig = await firstValueFrom(http.get(configUrl).pipe(timeout(3000), catchError((err) => { if (err.name === 'TimeoutError') { return throwError(() => new Error(`[RuntimeInitializer] Timeout: Could not load config from ${configUrl}`)); } if (err instanceof HttpErrorResponse) { return throwError(() => new Error(`[RuntimeInitializer] Failed to load config from ${configUrl}: ${err.status} ${err.statusText}`)); } return throwError(() => err); }))); if (runtimeConfig.coreConfig.debug) { console.log(runtimeConfig); } if (!runtimeConfig?.coreConfig) { throw new Error('[RuntimeInitializer] Missing coreConfig in config.json'); } if (!runtimeConfig.coreConfig.apiURL) { throw new Error('[RuntimeInitializer] coreConfig.apiURL is required'); } if (typeof runtimeConfig.coreConfig.debug !== 'boolean') { throw new Error('[RuntimeInitializer] coreConfig.debug must be boolean'); } if (typeof runtimeConfig.coreConfig.requestTimeout !== 'number') { throw new Error('[RuntimeInitializer] coreConfig.requestTimeout must be a number'); } appService.setConfig(new AppConfig(runtimeConfig, true)); if (postInit) { await postInit(appService.getConfig(), injectedServices); } return of(runtimeConfig); } catch (error) { console.error('Failed to load runtime config', error); const fallbackConfig = new AppConfig(); appService.setConfig(fallbackConfig); if (handleInitializationFailure) { handleInitializationFailure(error, injectedServices); } return of(fallbackConfig); } }); } /* * Public API Surface of ngx-runtime-initializer */ /** * Generated bundle index. Do not edit. */ export { AppConfig, AppConfigService, InMaintenanceGuard, MaintenanceGuard, RuntimeInitializerOptions, createStatusGuard, provideRuntimeInitializer }; //# sourceMappingURL=ngx-runtime-initializer.mjs.map