angular

Using Environment Variables in Angular

Reading Time: 5 minutes

Overview

If you are writing a Angular application chances are you will need to different settings for different environments.

For instance, assuming your angular app makes WebAPI calls, your rest api may be on another server or port based on the environment you are working in (dev, qa, staging, mocks, production, demo, etc).

When using the Angular CLI to generate your project, you already have a good starting pointing for Dev and Production, if not it’s simple to add them manually. However the documentation to add additional environments isn’t extremely straight forward. Hopefully this article will change that.

Please note Angular v2-5 is slightly different than Angular 6. This article will address both.

Regardless of the version, you still use the standard `environment.ts` & `environment.xxx.ts` files.

When Angular is running it will always read the environment.ts file. The trick is to have Angular override/replace that file with the correct one during the build process. The key difference between v2-5 and v6.x+ is how the files are overwritten. This will be explained later in the article.

If you used the Angular CLI, then you should see two files `environment.ts` files generated for you. Both are located in the `/src/environment` folder. One is named `environment.ts` and the other is `environment.prod.ts`.

Your development one will look like the following:

export const environment = {
production: false,
};

And your production one will look like this:

export const environment = {
production: true,
};

These files are just starting points and you can add anything you need to suite your needs. You can also add additional files for other environments (which we’ll do shortly). Since Angular is typically expecting the `production` flag, I always recommend keeping it. Just set it to false in any other environment.ts file you create (except the production one).

NOTE: For an example to see where Angular uses this flag, see `main.ts`

if (environment.production) {
enableProdMode();
}

Customizing

First let’s customize the files by adding a few new settings. My app will contain some rest api’s and I know that during development the Url will be different than that of production. In addition I’m using Okta as the authentication mechanism. With Okta, I’ll have different url(s) and client id(s) for my development vs. production environment.

I’ll add the following update:

 

Development: (environment.ts)

export const environment = {
    production: false,
    name: 'dev',
    restApi: { uri: 'https://localhost:5001' } ,

    Okta: {
         // url to
         baseUrl: 'https://dev-xxxxx.oktapreview.com',
        // dev client id
        clientId: 'dev-xxxxxxxxxxxxx'
    }
};

 

Production: (environment.prod.ts)

export const environment = {
    production: true,
    name: 'prod',
    restApi: { uri: 'https://api.myproduct.mycompany.com' } ,

    Okta: {
         // production okta api
        baseUrl: 'https://mycompnay.okta.com',
        // production client id
        clientId: 'prod-xxxxxxxxxxxx'
    }
};

Adding a new environment file

Let’s suppose you have a QA or Staging area which required different values from development or production. You can easily add a new environment.ts file. Typically the pattern is something like this environment.[your-new-environment].ts

For Example, lets add a QA and a Mock environment. The QA, Development and Production will all point to some real rest-api, however the Mock will point to the current projects /assets/ directory where we’ll store some local .json files. This way we can test locally without firing up another web server to do processing. Granted, the api’s in the Mock can’t perform full CRUD operations but it’s still a nice feature to use when testing data loads.

Here’s our QA environment file: (environment.qa.ts)

export const environment = {
    production: false,
    name: 'qa',
    restApi: { uri: 'https://my-qa-url.us-east-1.elasticbeanstalk.com' } ,

    Okta: {
        // url to
        baseUrl: 'https://dev-xxxxx.oktapreview.com',
        // dev client id
        clientId: 'qa-xxxxxxxxxxxxxx'
    }
};

 

Here’s our Mock environment file: (environment.mock.ts)

export const environment = {
    production: false,
    name: 'qa',
    restApi: { uri: '/assest/mocks' } ,

    Okta: {
        // url to
        baseUrl: 'https://dev-xxxxx.oktapreview.com',
        // dev client id
        clientId: 'mock-xxxxxxxxxxxxxx'
    }
};

Getting Angular to ‘see’ your new files

Now, you’ll need to tell angular where your file are. If you aren’t adding any new files, Angular will switch between development and production automatically based on your build flags.

Depending on your version of Angular and the Angular CLI, you’ll have a slight change.

Angular v2-5

Modify your angular-cli.json file, and add any environment you need

 

"environments": {
    "dev": "environments/environment.ts",
    "prod": "environments/environment.prod.ts",
    "qa": "environments/environment.qa.ts",
    "mock": "environments/environment.mock.ts"
}

 

To create a build with your correct environment

ng build --env=staging

 

To serve / launch the app with the correct environment

ng serve --env=staging

Angular 6.x+

Angular v6.x made this process a bit more generic, however it adds some extra steps. Instead of using the `env` flag, you will use the `–configurations` or `-c` shortcut flag to indicate the configuration you want to use. You will also need to instruct Angular which files to replace. While this adds some additional steps, it actually creates a lot of additional flexibility (you can use this functionality to customize your build any way you see fit).

Instead of modifying the angular-cli.json, you will modify the angular.json file. Find the `architect` section, then the `configurations` section. Simply copy the `production` – `fileReplacements` and change it to your two new items.

 

"configurations": {
    "production": {
        "fileReplacements": [
             {
                  "replace": "src/environments/environment.ts",
                   "with": "src/environments/environment.prod.ts"
             }
        ],
        "optimization": true,
        "outputHashing": "all",
        "sourceMap": false,
        "extractCss": true,
        "namedChunks": false,
        "aot": true,
        "extractLicenses": true,
        "vendorChunk": false,
        "buildOptimizer": true
    },
 "mock": {
     "fileReplacements": [
        {
            "replace": "src/environments/environment.ts",
            "with": "src/environments/environment.mock.ts"
        }
      ]
 },
"qa": {
     "fileReplacements": [
        {
            "replace": "src/environments/environment.ts",
             "with": "src/environments/environment.qa.ts"
        }
    ]
  }
}

Launching with ng serve

If you plan on ‘live/local’ testing the settings, you’ll also want to update the `serve` settings. This tells ng server to execute the correct build pattern. If you fail to update this section, you see an error similar to:

Configuration '[environment-name-here]' could not be found in project 'ClientApp'.

 

Example `serve` configuration matching the build section above.

"serve": {
    "builder": "@angular-devkit/build-angular:dev-server",
    "options": { "browserTarget": "ClientApp:build" },
    "configurations": {
        "production": {
            "browserTarget": "ClientApp:build:production"
        },
        "mock": {
             "browserTarget": "ClientApp:build:mock"
        },
        "qa": {
            "browserTarget": "ClientApp:build:qa"
        }
    }
}

 

The next time you run your app via `ng serve`, you can specify the environment via the configuration flag:

Special note: the flag is singular `configuration` not plural `configurations`

 

Launching the mocked version

ng serve --configuration mock

or

ng serve -c mock

 

Launching the QA version

ng serve --configuration qa

or

ng serve -c qa

Where’s my environment.dev.ts file?

You may be wondering ‘where’s the dev configurations settings?’.  There isn’t one.  By default your environment.ts file is considered your ‘development’ file, so there’s no reason to override it.

Consuming the settings

If use the environment.ts file on a regular basis, then this is a no-brainer but if you’re new to this, you may be wondering how to it all fits together. It’s actually really simple. You simply add a reference to the file in your Angular components or services that need the settings. Remember the environement.ts file will be overridden with the correct values.
So you only need to reference the environment.ts file.

Below is an example using the environment.ts file, which will automatically updated during my `build` or `serve` command as long as I provide the `-c` and the correct environment name, if not it will not override the file and I’ll have my development settings.

The API Service

I typically wrap my api calls into a service. This allows for central processing

import { Injectable } from "@angular/core";
import { environment } from '../../../../environments/environment';
import { Location } from "@angular/common";

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

    private url: string;
    constructor(private location: Location) {

        this.url = environment.restApi.uri;

    }

    // get the url we need for our api's
    getUrl(path: string = '') {

       if (path != null) {
           if (environment.name === 'mock') {
               // add the .json extension to the end for our mock files
               return this.url + path + ".json";
           } else {
               return this.url + path;
           }
       }

       return this.url;

   }
}

The action service

Now any action service can call the ApiService, which automatically sets the correct Url based on the environment.

I typically use enveloping with my RestApi’s, which is a design choice. If you’re interested in Enveloping

 

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { IUser } from '../../models/user.model';
import { Envelope } from '../../../models/api/envelope.model'

import { Observable } from 'rxjs';
import { ApiUtilityService } from './api-utility';

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

   constructor(private http: HttpClient, private apiService: ApiUtilityService) { }

   getList(): Observable<Envelope<IUser[]>> {

      return this.http.get<Envelope<IUser[]>>(this.apiService.getUrl(`/api/users`));
   }

   getItem(userId: number): Observable<Envelope<IUser>> {

      return this.http.get<Envelope<IUser>>(this.apiService.getUrl(`/api/users/${userId}`));
   }

  createItem(config: IUser) {

      //this.http.post()
  }

}