Dynamic environment variable
Tags: angular, javascript
Published: 2020-01-17 22:57:49

### One build to rule them all!
Imagine that you have a multi-tenant product. Building your angular app for every client is a drag. I refuse to do the same build over and over again. Just to have different environment settings. So how to fix this. 
I found a few posts online that helped me with this problem. In short, there are 2 different ways of doing this. One way is with making a window object dirty ( i don’t like this ). The other is a more angular way. So I will show you that way. 
In both ways, the common denominator is a secret gem. APP_INITIALIZER.

###So what is APP_INITIALIZER?
>A function that will be executed when an application is initialized.

The official documentation says just that. Not very helpful. Right. 
Let's start coding.
app-init.service.ts 
```javascript
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import { HttpClient } from '@angular/common/http';
import { EnvironmentService } from './environment.service';

@Injectable({
  providedIn: 'root'
})
export class AppInitService {

  /** config.js file that will contain out environment variables */
  private readonly CONFIG_URL = 'assets/config/config.js';
  private config$: Observable<any>;
  
  constructor(
    private http: HttpClient,
    private environmentService: EnvironmentService
    ) { }
  
    /**
     * Method for loading configuration
     */
    loadConfiguration(){
      if(this.config$ && environment.production){
        this.config$ = this.http.get(this.CONFIG_URL)
        .pipe(
          shareReplay(1)
        );
      } else {
        this.config$ = of(environment);
      }

      this.environmentService.setEnvironment(this.config$);
      return this.config$;
  }
}
``` 
environment.service.ts
```javascript
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class EnvironmentService {

  private dynamicEnv: any;

  constructor() { }
  /** Setter for environment variable */
  setEnvironment(env: Observable<any>){
    env.subscribe(data => this.dynamicEnv = { ...data});
  }
  /** Getter for environment variable */
  get environment() {
    return this.dynamicEnv;
  }
}

```
app.module.ts
```javascript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';

import { AppComponent } from './app.component';
import { AppInitService } from './app-init.service';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [
    {
      // This is where the magic happens. NOTE we are returning an Observable and converting it to Promise
      // IMPORTANT It has to be a Promise 
      provide: APP_INITIALIZER,
      useFactory: (appInit: AppInitService) => () => appInit.loadConfiguration().toPromise(),
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
```
This is the basic setup for a dynamic environment. We are bypassing the __environment__ and delegating the service to take care of our environment for us. In my example, we will put the configuration in the __config.json__ file and in the __environment.prod.ts__ we will set production to true.
This way __app-init.service.ts__ will know what configuration to load. If we are in development it will load __environment.ts__ configuration and if we are in production it will load __config.json__.
U can call __API__ instead of loading __config.json__ if you want. 
__IMPORTANT__ Be careful with interceptors.  Your configuration will be undefined until __config.json__ loads. So your interceptor (if you have them) need to ignore the first initial post (a post that angular need before initialization).

####UPDATE
It was put to my attention that this post is unclear on how to implement this concept on multi-tenant applications. You have your one application build and you need to install it on different domains with there own settings. So just need to add your config.json to assets/config/ with your environment to it. That it. 

{% github https://github.com/ikonezg/ng-dynamic-environment %}