npm package: @sigao/ng-app-config
Description: This library is used for loading application configuration settings from a local JSON file.  By loading from a JSON file, users are able to change applications settings via a build process without rebuilding the application, as is the case with Angular's
Latest version: 13.0.2

# NgAppConfig

This library is used for loading application configuration settings from a local JSON file.  By loading from a JSON file,
users are able to change applications settings via a build process without rebuilding the application, as is the case with Angular's environment variables.

**NOTE: This method of managing application variables does not in any way obfuscate values from the client.  Never put sensitive values in your configuration file.**

## Basic Usage

### Install the package

Run `npm install @sigao/ng-app-config` 

### Create the configuration json

Place a file with the extension ".json" in your project.  For the sake of convenience, it is recommended that
you place the file directly in the "src" folder as this is where angular looks for assets by default.


### Include the file in your application

By default, Angular will not recognize your configuration file as a file to be included with the package. To change this, open your 
angular.json file and find the "assets" property of your projects (projects > YOUR_PROJECT > architect > build > assets).  Add the file path to the array like so:

```
 "assets": [
              "src/favicon.ico",
              "src/assets",
              "src/MY_CONFIG.json"
            ],
```


### Import the module

Import the module as well as a path to your file. If you placed your configuration file in the 'src' folder you will not need anything else but the file name. 

```
const pathToConfigurationFile = 'MY_CONFIG.json';

@NgModule({
  declarations: [AppComponent],
  imports: [
    NgAppConfigModule.forRoot(pathToConfigurationFile)
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

### Use the service

Any component/service that needs configuration settings can now inject the NgAppConfigService.  The type you define for the service will be the type of the config object.

**NOTE: Due to possible loss of functionality from cloning the config object, this object CAN be edited and will behave as any other singleton.  It is up to you to prevent modification of this object**

```
import NgAppConfigService from "@sigao/ng-app-config";

@Injectable()
export class MyService {

constructor(public configService: NgAppConfigService<MyConfigType>){
	console.log(configService.config)
	}
}
```

## Post Load Configuration

Because this library is designed to configure other aspects of the system, it may be necessary to configure other services before the application finishes loading. To do this, you will need to send a configuration object to the NgAppConfig module.

### The post load object

```
{
  // This confunction is used to convert the raw JSON object BEFORE it is provided to the service.  
  // For example, if you want to turn your configuration object into a typescript class with
  // methods, this is where you would do that.
  customConverter?: (jsonValue: any) => any,

  // [Default: true] This flag decides if the post load actions will run sequentially or in tandem. 
  // NOTE: setting this to false could seriously impact startup performance, be sure
  // you ABSOLUTELY need sequential execution before setting this to false 
  async?: boolean,

  // This array contains a list of configuration actions to be run using the configuration result.
  actions?: {

      // Injection token of the service that needs to be configured.
      provide: any,

      // This function must be a promise.  Will execute with the provided service and 
      // loaded config object AFTER customConverter has run.

      // [config]: The config object post conversion
      // [provided]: The service as provided by the injection token
      // [result?]: The resolved value of the previous action (Only provided 
      // when "async" is set to false, otherwise undefined)
      do: (config: any, provided: any, result?: any) => Promise<any | void>
  }[]
}
```

### [Example] Tricking the AOT compiler

When compiling AOT, the Angular CLI will not like inline annonymous functions.  To get around this (and make things cleaner), make a seperate file in your app folder.  We'll name ours "config.ts".

**config.ts**
```
// Define the object
export const postLoad = {
    customConverter,
    async: false,
    actions: [
        {
            provide: TestService,
            do: configFunction
        }
    ]
};

// Export the functions to appease to AOT compiler
export function customConverter(val) {
    // Conversions happen here!
    return val;
}
export function configFunction(loadedConfig, service: TestService, val) {
    console.log('Should be "undefined":', val === undefined);
    return service.returnAfterWait(3000, 'A');
}
```

Then, import it into your app.module and send it into the module definition:

```
import { postLoad } from './config.ts';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    NgAppConfigModule.forRoot('test_config.json', postLoad)
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

### [Example] async = true

In this example we're providing a test service with a simple function that waits a variable number of seconds then resolves some value.  Because the actions will resolve async, we resolve with null as the results can't be captured.  In this instance, the actions will resolve in order of shortest wait to longest wait, regardless of their order in the array.  


Total load time: 3000ms + inital config load

```
import { TestService } from './test.service';

const postLoadAsync = {
    async: true,
    actions: [
        {
            provide: TestService,
            do: config1
        },
        {
            provide: TestService,
            do: config2
        },
        {
            provide: TestService,
            do: config3
        },
    ]
};

export function config1(loadedConfig, service: TestService) => {
    return service.returnAfterWait(2000, null).then(() => console.log('Second')); // Resolves second
}
export function config2(loadedConfig, service: TestService) => {
    return service.returnAfterWait(3000, null).then(() => console.log('Last'));  // Resolves last
}
export function config3(loadedConfig, service: TestService, val) => {
    return service.returnAfterWait(1000, null).then(() => console.log('First')); // Resolves first
}
```

### [Example] async = false

In this example the actions will execute in sequence.  Because of this, we are able to save the resolved value of the previous action and provide it to the next action in line.  This should ONLY be used if your services take very little time to configure, or you're resolving immediately by returning "Promise.resolve()".


Total load time: 9000ms + inital config load

```
import { TestService } from './test.service';

// Define the object
export const postLoadSync = {
    async: false,
    actions: [
        {
            provide: TestService,
            do: configFunction1
        },
        {
            provide: TestService,
            do: configFunction2
        },
        {
            provide: TestService,
            do: configFunction3
        },
    ]
};

export function configFunction1(loadedConfig, service: TestService, val) {
    console.log('Should be "undefined":', val === undefined);
    return service.returnAfterWait(3000, 'A'); // Resolves first
}
export function configFunction2(loadedConfig, service: TestService, val) {
    console.log('Should be "A":', val);
    return service.returnAfterWait(3000, 'B'); // Resolves second
}
export function configFunction3(loadedConfig, service: TestService, val) {
    console.log('Should be "B":', val);
    return service.returnAfterWait(3000, 'C'); // Resolves last
}

```


## Change Log

## 13.0.2 Angular 13
* Updated to support Angular 13
* Updated major version to match Angular major version

## 2.1.0 Angular 10

* Upgraded peer dependencies to target Angular 10
* Tested functionality against Angular 10
* Improved unit testing

### 2.0.1 - 2.0.2 New org!

* Migrated package to our company org

### 2.0.0

* Refactored extranious functionality out of config service
* * Config object is now accessed using "config" property rather than "getConfig()"
* * Removed "path" property from config service
* Added all post load functionality
* Moved custom converter into post load configuration object rather than direct import into moduleforRoot

## Additional Info

Special thanks to [https://jbt.github.io/markdown-editor/](https://jbt.github.io/markdown-editor/) for helping me learn README markdown.


## Powered By [Sigao](http://www.sigao.io/)

This package is designed to be used within our tech stack to support our development efforts.  While significant effort has been made to ensure that these packages are tested and maintained, mistakes happen, and there is a good possibility the bug won't be addressed unless it directly impacts our specific use case.   

If you encounter a bug that you'd like addressed, feel free to reach out to us and we'll see what we can do