@dagonmetric/ng-config
Version:
Configuration and options service for Angular applications.
490 lines (482 loc) • 15.6 kB
JavaScript
import { InjectionToken, EventEmitter, ɵɵdefineInjectable, ɵɵinject, INJECTOR, Injectable, Inject, Injector, Optional, APP_INITIALIZER, NgModule } from '@angular/core';
import { Observable, of, forkJoin } from 'rxjs';
import { tap, map, mapTo, share, take } from 'rxjs/operators';
/**
* @fileoverview added by tsickle
* Generated from: src/config-options.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const CONFIG_OPTIONS = new InjectionToken('ConfigOptions');
/**
* @fileoverview added by tsickle
* Generated from: src/config-provider.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const CONFIG_PROVIDER = new InjectionToken('ConfigProvider');
/**
* @fileoverview added by tsickle
* Generated from: src/logger.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const NG_CONFIG_LOGGER = new InjectionToken('NG-CONFIG Logger');
/**
* @fileoverview added by tsickle
* Generated from: src/util.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @param {?} options
* @param {?} configSection
* @return {?}
*/
function mapOptionValues(options, configSection) {
/** @type {?} */
const keys = Object.keys(options);
for (const key of keys) {
if (!Object.prototype.hasOwnProperty.call(configSection, key)) {
continue;
}
// const popDescriptor = Object.getOwnPropertyDescriptor(options, key);
// if (!popDescriptor?.writable) {
// continue;
// }
/** @type {?} */
const optionsValue = options[key];
/** @type {?} */
const configValue = configSection[key];
if (configValue == null) {
options[key] = null;
continue;
}
if (optionsValue == null) {
options[key] = configValue;
continue;
}
if (optionsValue === configValue) {
continue;
}
if (typeof optionsValue === 'string') {
if (typeof configValue === 'string') {
options[key] = configValue;
}
else {
options[key] = JSON.stringify(configValue);
}
}
else if (typeof optionsValue === 'boolean') {
if (typeof configValue === 'boolean') {
options[key] = configValue;
}
else if (typeof configValue === 'string') {
options[key] = ['true', '1', 'on', 'yes'].indexOf(configValue.toLowerCase()) > -1;
}
else if (typeof configValue === 'number') {
options[key] = configValue === 1;
}
else {
options[key] = false;
}
}
else if (typeof optionsValue === 'number') {
options[key] = Number(configValue) || 0;
}
else if (Array.isArray(optionsValue)) {
if (Array.isArray(configValue)) {
if (configValue.length > 0 &&
configValue.filter((/**
* @param {?} v
* @return {?}
*/
(v) => typeof v == 'string')).length === configValue.length) {
options[key] = [...configValue];
}
else {
options[key] = [];
}
}
else if (typeof configValue === 'string') {
options[key] = configValue
.split(';')
.map((/**
* @param {?} s
* @return {?}
*/
(s) => s.trim()))
.filter((/**
* @param {?} s
* @return {?}
*/
(s) => s.length > 0));
}
}
else if (typeof optionsValue === 'object' &&
Object.prototype.toString.call(optionsValue) !== '[object Date]') {
if (!Array.isArray(configValue) && typeof configValue === 'object') {
mapOptionValues(optionsValue, configValue);
}
}
}
}
/**
* @param {?} a
* @param {?} b
* @return {?}
*/
function equalDeep(a, b) {
if (a === null && b === null) {
return true;
}
if (Array.isArray(a)) {
if (!b || !Array.isArray(b)) {
return false;
}
if (a.length !== b.length) {
return false;
}
for (let i = a.length - 1; i >= 0; i--) {
if (!equalDeep(a[i], b[i])) {
return false;
}
}
return true;
}
if (Array.isArray(b)) {
return false;
}
if (a && b && typeof a == 'object' && typeof b == 'object') {
/** @type {?} */
const keys = Object.keys(a);
if (keys.length !== Object.keys(b).length) {
return false;
}
for (const key of keys) {
if (!equalDeep(a[key], b[key])) {
return false;
}
}
return true;
}
return a === b;
}
/**
* @fileoverview added by tsickle
* Generated from: src/config.service.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* The core service for loading and getting configuration value the from configuration providers.
*/
class ConfigService {
/**
* @param {?} configProviders
* @param {?} injector
* @param {?=} options
* @param {?=} logger
*/
constructor(configProviders, injector, options, logger) {
this.injector = injector;
this.loading = false;
this.activated = false;
this.currentLoad = new Observable();
this.loadedConfig = {};
this.optionsRecord = new Map();
this.sortedConfigProviders = configProviders.reverse();
this.options = options || {};
this.valueChanges = new EventEmitter();
if (logger) {
this.logger = logger;
}
else {
this.logger = {
debug: (/**
* @param {?} message
* @param {?} data
* @return {?}
*/
(message, data) => {
if (data) {
// eslint-disable-next-line no-console
console.log(`${message}, data: `, data);
}
else {
// eslint-disable-next-line no-console
console.log(message);
}
})
};
}
this.currentLoad = this.initLoad();
this.subscribeCurrentLoad(false);
}
/**
* @return {?}
*/
get providers() {
return this.sortedConfigProviders;
}
/**
* Call this method to ensure configurations are fetched and activated.
* @return {?}
*/
ensureInitialized() {
if (this.activated) {
return of(this.activated);
}
return this.currentLoad.pipe(tap((/**
* @param {?} config
* @return {?}
*/
(config) => {
this.activateConfig(config, false);
})), map((/**
* @return {?}
*/
() => this.activated)));
}
/**
* Call this method to reload fresh configuration values from config providers.
* @return {?}
*/
reload() {
this.currentLoad = this.initLoad();
this.subscribeCurrentLoad(true);
return this.currentLoad.pipe(mapTo(void 0));
}
/**
* Use this method to get loaded configuration value with a given string key.
* @param {?} key The config key string.
* @return {?}
*/
getValue(key) {
return this.getConfigValue(key, this.loadedConfig);
}
/**
* Use this method to map loaded configuration values to the instance of options class type.
* @template T
* @param {?} key The config key string.
* @param {?} optionsClass The options class type to be mapped.
* @return {?}
*/
mapType(key, optionsClass) {
/** @type {?} */
const optionsObj = this.injector.get(optionsClass, new optionsClass());
this.mapObject(key, optionsObj);
return optionsObj;
}
/**
* Use this method to map loaded configuration values to the options object.
* @template T
* @param {?} key The config key string.
* @param {?} optionsObj The options object to be mapped with configuration values.
* @return {?}
*/
mapObject(key, optionsObj) {
/** @type {?} */
const cachedOptions = (/** @type {?} */ (this.optionsRecord.get(key)));
if (cachedOptions != null) {
if (cachedOptions === optionsObj) {
return cachedOptions;
}
this.optionsRecord.delete(key);
}
/** @type {?} */
const configValue = this.getValue(key);
if (configValue == null || Array.isArray(configValue) || typeof configValue !== 'object') {
return optionsObj;
}
mapOptionValues((/** @type {?} */ (optionsObj)), configValue);
this.optionsRecord.set(key, optionsObj);
return optionsObj;
}
/**
* @private
* @return {?}
*/
initLoad() {
if (this.currentLoadSubscription) {
this.currentLoadSubscription.unsubscribe();
this.currentLoadSubscription = null;
}
if (!this.loading) {
this.log('Cconfiguration loading started.');
this.loading = true;
}
return forkJoin(this.providers.map((/**
* @param {?} configProvider
* @return {?}
*/
(configProvider) => {
/** @type {?} */
const providerName = configProvider.name;
/** @type {?} */
const loadObs = configProvider.load().pipe(tap((/**
* @param {?} config
* @return {?}
*/
(config) => {
this.log(providerName, config);
})), share());
return loadObs.pipe(take(1), share());
}))).pipe(map((/**
* @param {?} configs
* @return {?}
*/
(configs) => {
/** @type {?} */
let mergedConfig = {};
configs.forEach((/**
* @param {?} config
* @return {?}
*/
(config) => {
mergedConfig = Object.assign(Object.assign({}, mergedConfig), config);
}));
return mergedConfig;
})));
}
/**
* @private
* @param {?} reActivate
* @return {?}
*/
subscribeCurrentLoad(reActivate) {
this.currentLoadSubscription = this.currentLoad.subscribe((/**
* @param {?} config
* @return {?}
*/
(config) => {
this.activateConfig(config, reActivate);
}), (/**
* @return {?}
*/
() => {
this.loading = false;
}));
}
/**
* @private
* @param {?} config
* @param {?} reActivate
* @return {?}
*/
activateConfig(config, reActivate) {
this.loading = false;
if (this.activated && !reActivate) {
return;
}
if (!equalDeep(config, this.loadedConfig)) {
this.optionsRecord.clear();
this.loadedConfig = config;
this.activated = true;
this.log('Configuration loading completed.');
((/** @type {?} */ (this.valueChanges))).emit(config);
}
else {
this.activated = true;
this.log('Configuration loading completed.');
}
}
/**
* @private
* @param {?} key
* @param {?} config
* @return {?}
*/
getConfigValue(key, config) {
/** @type {?} */
const keyArray = key.split(/:/);
/** @type {?} */
const result = keyArray.reduce((/**
* @param {?} acc
* @param {?} current
* @return {?}
*/
(acc, current) => acc && acc[current]), config);
if (result === undefined) {
return null;
}
return result;
}
/**
* @private
* @param {?} msg
* @param {?=} data
* @return {?}
*/
log(msg, data) {
if (!this.options.debug) {
return;
}
this.logger.debug(`[ConfigService] ${msg}`, data);
}
}
/** @nocollapse */ ConfigService.ɵprov = ɵɵdefineInjectable({ factory: function ConfigService_Factory() { return new ConfigService(ɵɵinject(CONFIG_PROVIDER), ɵɵinject(INJECTOR), ɵɵinject(CONFIG_OPTIONS, 8), ɵɵinject(NG_CONFIG_LOGGER, 8)); }, token: ConfigService, providedIn: "root" });
ConfigService.decorators = [
{ type: Injectable, args: [{
providedIn: 'root'
},] }
];
/** @nocollapse */
ConfigService.ctorParameters = () => [
{ type: Array, decorators: [{ type: Inject, args: [CONFIG_PROVIDER,] }] },
{ type: Injector },
{ type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [CONFIG_OPTIONS,] }] },
{ type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [NG_CONFIG_LOGGER,] }] }
];
/**
* @fileoverview added by tsickle
* Generated from: src/config.module.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @param {?} configService
* @return {?}
*/
function configAppInitializerFactory(configService) {
/** @type {?} */
const res = (/**
* @return {?}
*/
async () => configService.ensureInitialized().toPromise());
return res;
}
/**
* The `NGMODULE` for providing `ConfigService`. Call `configure` method to provide options for `ConfigService`.
*/
class ConfigModule {
/**
* Call this method in root module to provide options for `ConfigService`.
* @param {?=} loadOnStartUp If `true` configuration values are loaded at app starts. Default is `true`.
* @param {?=} options Option object for `ConfigService`.
* @return {?}
*/
static configure(loadOnStartUp = true, options = {}) {
return {
ngModule: ConfigModule,
providers: [
{
provide: CONFIG_OPTIONS,
useValue: options
},
loadOnStartUp
? {
provide: APP_INITIALIZER,
useFactory: configAppInitializerFactory,
deps: [ConfigService],
multi: true
}
: []
]
};
}
}
ConfigModule.decorators = [
{ type: NgModule, args: [{
providers: [ConfigService]
},] }
];
export { CONFIG_OPTIONS, CONFIG_PROVIDER, ConfigModule, ConfigService, NG_CONFIG_LOGGER, configAppInitializerFactory };
//# sourceMappingURL=ng-config.js.map