npm package: @nxt-engineering/ngx-runtime-config
Description: This project provides a way to load runtime configuration for an Angular application from a remote endpoint during the initialization of Angular. This is useful for scenarios where configuration values need to be dynamic and can't be hard-coded into the a
Latest version: 0.0.10

# ngx-runtime-config

This project provides a way to load runtime configuration for an Angular application from a remote endpoint during the initialization of Angular.
This is useful for scenarios where configuration values need to be dynamic and can't be hard-coded into the application at build time.

We mostly use this for getting the OIDC configuration for the current environment,
for delivering access tokens and configuration for third party services,
and to implement feature flags,
allowing us to dynamically enable and disable certain features without redeployment.
We use it to initialize services like
[angular-oauth2-oidc][github_angular-oauth2-oidc],
[angular-auth-oidc-client][github_angular-auth-oidc-client],
[Turnstile][cf_turnstile],
[Sentry][sentry],
[Matomo][matomo],
[Clarity][npmjs_clarity]
and basically any other service.

[github_angular-oauth2-oidc]: https://github.com/manfredsteyer/angular-oauth2-oidc
[github_angular-auth-oidc-client]: https://github.com/damienbod/angular-auth-oidc-client
[sentry]: https://sentry.io/
[npmjs_clarity]: https://www.npmjs.com/package/@microsoft/clarity
[matomo]: https://matomo.org/
[cf_turnstile]: https://developers.cloudflare.com/turnstile/

This project builds on [`APP_INITIALIZER`](https://angular.dev/api/core/APP_INITIALIZER),
so that the configuration is loaded during the initialization phase of Angular.
This way, the configuration is available early on.

The configuration is cached,
so that reloading the window does not require another round-trip to the server.
But for the first initialization, you pay a small penalty,
as the application needs another roundtrip to the server.

## Getting Started

Install the package:

```sh
npm install @nxt-engineering/ngx-runtime-config
```

Then create an interface for your application's configuration needs, like so:

```ts
// src/app/app.runtime-config.ts
import { OpenIdConfiguration } from "angular-auth-oidc-client";

export interface AppRuntimeConfig {
  server: {
    // useful information about the server to render in the footer
    env: string;
    version: string;
  };

  featureFlags?: {
    featureA?: boolean;
    featureB?: number; // might be a ratio, e.g. 0.5
    featureC?: string; // might be a pattern, e.g. `@my-org.com`
    featureD?: string[]; // might be a list of accounts, e.g. `["carla", "beat", "test123"]`
  };

  auth: OpenIdConfiguration; // you can re-use interfaces of other components
}
```

Then extend the `providers` array where you bootstrap your application,
usually in the `app.config.ts` or the `main.ts` file:

```ts
// src/app/app.config.ts

import { ApplicationConfig, provideZoneChangeDetection } from "@angular/core";
import { provideRouter } from "@angular/router";
import { provideRuntimeConfig } from "@nxt-engineering/ngx-runtime-config";

import { routes } from "./app.routes";
import { AppRuntimeConfig } from "./app.runtime-config";

export const appConfig: ApplicationConfig = {
  providers: [provideRuntimeConfig<AppRuntimeConfig>(), provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)],
};
```

If you don't provide a configuration URL, the default URL is `/.well-known/runtime-config`.
The expected response is a JSON that matches the interface you defined.
As an example, this JSON matched the interface defined above.

```json
{
  "server": {
    "env": "prod",
    "version": "v42-f321ba9"
  },
  "featureFlags": {
    "featureB": 1.0
  },
  "auth": {
    "authority": "https://my-app.zitadel.cloud/",
    "clientId": "112233445566778899@backoffice",
    "scope": "openid profile email offline_access",
    "responseType": "code"
  }
}
```

## Advanced Usage

You have several options to deliver this file:

- Have your application server handle that pre-configured URL (or place a file there)
- Configure your application server, web server or CDN to redirect with `301 Moved Permanently` to the actual endpoint
- Configure the actual endpoint in code, like in the following example

```ts
// src/app/app.config.ts

import { ApplicationConfig, provideZoneChangeDetection } from "@angular/core";
import { provideRouter } from "@angular/router";
import { provideRuntimeConfig } from "@nxt-engineering/ngx-runtime-config";

import { routes } from "./app.routes";
import { AppRuntimeConfig } from "./app.runtime-config";

export const appConfig: ApplicationConfig = {
  providers: [
    provideRuntimeConfig<AppRuntimeConfig>({
      configEndpoint: "/api/v2/client-config",

      autoLoad: true, // default
      cacheBehavior: "sessionStorage", // default
      cacheValidityMillis: 1000 * 60 * 60, // default
    }),
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
  ],
};
```

The configuration is automatically loaded during the bootstrap phase of Angular (unless you opt out).
If the value is already cached, it will not be fetched a second time.
By default, the configuration is cached in the [sessionStorage][mdn_SessionStorage] for one hour.

If your application is often opened in multiple tabs simultaneously,
then you should consider changing the cache to the [localStorage][mdn_LocalStorage]:

[mdn_SessionStorage]: https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage
[mdn_LocalStorage]: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage

```ts
// src/app/app.config.ts

import { ApplicationConfig, provideZoneChangeDetection } from "@angular/core";
import { provideRouter } from "@angular/router";
import { provideRuntimeConfig } from "@nxt-engineering/ngx-runtime-config";

import { routes } from "./app.routes";
import { AppRuntimeConfig } from "./app.runtime-config";

export const appConfig: ApplicationConfig = {
  providers: [
    provideRuntimeConfig<AppRuntimeConfig>({
      cacheBehavior: "localStorage",
      // …
    }),
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
  ],
};
```

To read the configuration, you can inject the `NgxRuntimeConfigService` and access it's `config` property:

```ts
// src/app/app.component.ts
import { AppConfig } from "./app.config";
import { NgxRuntimeConfigService } from "@nxt-engineering/ngx-runtime-config";

export class AppComponent {
  constructor(readonly configService: NgxRuntimeConfigService<AppConfig>) {
    this.serverConfig = configService.config()?.server;
  }
}
```

This also allows you to reload the configuration, should that become necessary:

```ts
// src/app/app.component.ts
import { AppConfig } from "./app.config";
import { NgxRuntimeConfigService } from "@nxt-engineering/ngx-runtime-config";

export class AppComponent {
  constructor(readonly configService: NgxRuntimeConfigService<AppConfig>) {}

  async reloadConfig() {
    await this.configService.reload();
  }
}
```

By default, `reload()` will ignore the cache.

## Demo Projects

There are some demo projects to see how the `NgRuntimeConfigService` is used in a real Angular projects.

> **Note:**
>
> These projects load the runtime config file from `/runtime-config.json`.
> The corresponding JSON is provided as a static file in `public/runtime-config.json`.
> In a real project, this file would not be checked-in into your source code.
> Rather, it'd either be generated during deployment or on-demand by an application server.

### Fetch-based config loader

This project does not configure a `HttpClient`.
So the `NgRuntimeConfigService` relies on the native `fetch()` method to get the configuration from the server.
This is useful, if you need information from the runtime configuration to configure the `HttpClient`.

```bash
ng serve @nxt-engineering/demo-fetch
```

### HttpClient-based config loader

This project is configured with a `HttpClient`.

```bash
ng serve @nxt-engineering/demo-http
```

## Release

Instructions to publish a new release:

1. Set the version in [`./projects/nxt-engineering/ngx-runtime-config/package.json`](./projects/nxt-engineering/ngx-runtime-config/package.json).
2. `git commit -m 'prepare <the version>'`
3. `git tag <the version>`
4. `git push`
5. `git push --tags`

## License

This project is licensed under the MIT License, see the [LICENSE](LICENSE) file for details.