Build your Angular App Once, Deploy Anywhere
Tags: angular
Published: 2020-01-11 22:12:03

---
title: Build your Angular App Once, Deploy Anywhere
date: "2020-01-12T22:12:03.284Z"
description: "Want to build your Angular app once and deploy anywhere? Learn why compile-time configuration doesn't work and how we can implement a runtime configuration setup."
---

![Gears Image](./gears.png)

```
Angular version: 8.x 
Node Version: 10.9 or later
```

## The Ideal Scenario
We're building an Angular application and when we merge new code into the master branch of our git repo, we want our build tool (like Jenkins) to grab the latest code and build our deployment package for us. With our deployment package built (A.K.A the `dist` folder), we want to head over to our deployment tool (like Octopus), select an environment to which we want to deploy our app, click a "deploy" button, and trust it to deploy our package, replacing our envrionment variables in a config file with values specific to the selected environment.

## What Do We Need to Achieve This?
We need a configuration file that we can access from our Angular code at runtime - which means it has to exist in the `dist` folder we intend to deploy. We need it there because we want to configure our deployment tool to replace the values of the environment variables within with values specific to the environment we deploy to.

## Why Angular's Environment Files Are Not The Solution
Let's say we are using the _environment_ files for our configuration as described [here](https://angular.io/guide/build). If we run `ng build` and look inside of the `dist` folder, we do not see any of the environment files there. Because this is a compile-time solution, the configuration settings in the environment files are pulled into the minified JS bundles in the `dist` folder. We cannot easily configure our build tool to edit our environment variables if we cannot point it toward a file to edit. In short, this does not work with the "build once, deploy anywhere" model. To do this, our app needs to resolve configuration data at runtime instead of compile time. 

## So What Do We Do?
Luckily, there is a rather quick solution. All we have to do is:
1. Add a JSON configuration file in the `src` folder
2. Update our angular/webpack configuration to include the file in our `dist` folder
3. Add a simple configuration service with a call to get our config data from our config file
4. Use APP_INITIALIZER to invoke the method retrieving our config data during the bootstrap process

_Side note: Placing our configuration in a JSON file makes configuring our deployment tool easier because many of them (like Octopus) have native support for replacing values in JSON files._

### Adding the config file
There isn't much to this step. We're simply going to add a file named `app-config.json` and populate it with the following JSON.
```
{
  "api": "http://localhost:5000/"
}
```

### Ensuring the config file is copied to the `dist` folder
To achieve this, we need to make an addition to the webpack configuration in the `angular.json` file. We need to add the path to our config file to the `assets` array in the webpack `build` configuration.

![Screenshot - angular.json](./app-config.png)

### Building the service
This is a simple service with a private property and two methods - one that sets the property and another that exposes the config data for the rest of your app. We can type the config object with an interface to help ensure we get what we expect from the JSON config file. 
```
@Injectable({
  providedIn: 'root'
})
export class ConfigService {

  private configuration: AppConfig;

  constructor(
    private httpClient: HttpClient
  ) { }

  setConfig(): Promise<AppConfig> {
    return this.httpClient
      .get<AppConfig>('./app-config.json')
      .toPromise()
      .then(config => this.configuration = config);
  }

  readConfig(): AppConfig {
    return this.configuration;
  }
}
```

Notice that the `setConfigData` method returns a promise? The initialization of our app will not complete until all promises are resolved so by returning a promise here, we're ensuring that the config data will be available when the rest of our app loads up and needs to use it.

## Let's take a look

With that in place, let's set up the `APP_INITIALIZER`. According to the [docs](https://angular.io/api/core/APP_INITIALIZER), `APP_INITIALIZER` is an injection token that allows us to invoke functions during the bootstrapping process of our application. To do that, we add the `ConfigService` and `APP_INITIALIZER` token as providers in `app.module.ts`. 

```
const appInitializerFn = (configService: ConfigService) => {
  return () => {
    return configService.setConfig();
  };
};

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],
  providers: [
    ConfigService,
    {
      provide: APP_INITIALIZER,
      useFactory: appInitializerFn,
      multi: true,
      deps: [ConfigService]
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

Note that we need to use a factory function to create an instance of our `ConfigService` and call the `setConfig()` method on it.

Now, to ensure that this worked as expected, we can inject our `ConfigService` into the `AppComponent` and call our `readConfig()` method to get the config object.

```
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {

  config: AppConfig;

  constructor(private configService: ConfigService) {}

  ngOnInit(): void {
    this.config = this.configService.readConfig();
  }
}
```

In our `app.component.html` file, we will just remove all of the default boilerplate HTML and add the followig to display our config data.
```
<div>{{ config | json }}</div>
```

If we run our app locally with `ng serve`, we will see our JSON configuration object rendered on the webpage. 

To see a working example, take a look at the [GitLab Repo](https://gitlab.com/kylerjohnsondev/ng-runtime-config).