Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access environment variables in production build Angular 4

I want to deploy a production build of angular app with a configurable api url for the user to test it out. I use the environment.ts but after the production build, I do not know how to configure the variables.

What approach needs to be done?

like image 405
OreoFanatics Avatar asked Dec 22 '17 08:12

OreoFanatics


4 Answers

The environment*.ts file contain build time configurations, which you cannot change after the build. If you need to change your configuration after the build, you need to put them in a different place and retrieve them dynamically when the application starts

What you can do is:

Step #1: put your json configuration files under src/assets/config/[envName].json.

Note: it has to be json format, not ts format

Step #2: Add a new config service

import {Inject, Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {Observable} from 'rxjs/Rx';
import {environment} from "../../environments/environment";

/**
 * Declaration of config class
 */
export class AppConfig
{
//Your properties here
  readonly production: boolean;
  readonly name: string;

  readonly apiBaseUrl: string;

}

/**
 * Global variable containing actual config to use. Initialised via ajax call
 */
export let APP_CONFIG: AppConfig;

/**
 * Service in charge of dynamically initialising configuration
 */
@Injectable()
export class AppConfigService
{

  constructor(private http: HttpClient)
  {
  }

  public load()
  {
    return new Promise((resolve, reject) => {

      let confName = environment.name + '.json';
      this.http.get('/assets/config/' + confName).map(res => res as any).catch((error: any): any => {
        reject(true);
        return Observable.throw('Server error');
      }).subscribe((envResponse :any) => {
        let t = new AppConfig();
        //Modify envResponse here if needed (e.g. to ajust parameters for https,...)
        APP_CONFIG = Object.assign(t, envResponse);
        resolve(true);
      });

    });
  }
}

Step #3: In your main module, add this before declaring the module

/**
* Exported function so that it works with AOT
* @param {AppConfigService} configService
* @returns {Function}
*/
export function loadConfigService(configService: AppConfigService): Function 

{
  return () => { return configService.load() }; 
}

Step #4: Modify the module providers to add this providers: [ …

  AppConfigService,
  { provide: APP_INITIALIZER, useFactory: loadConfigService , deps: [AppConfigService], multi: true },


],

Step 5: In your code, instead of using environment.configXXX, use this

import {APP_CONFIG} from "../services/app-config.service";

//…
return APP_CONFIG.configXXX;

This is a simplified example, you'll actually need to make some changes if you use angular universal as you need to have absolute urls when making an http call

like image 105
David Avatar answered Oct 11 '22 14:10

David


Are you using Angular-CLI? It should be easy, then. You have something like this:

src/
  app/
  environment/
    environment.ts
    environment.prod.ts

Simply put different url in environment.prod.ts and your prod build gets a second url. E.g. let's say your environment.ts looks like this:

{
  "production": false,
  "apiUrl": "http://localhost:8080"
}

Put this in environment.prod.ts:

{
  "production": true,
  "apiUrl": "https://example.com/api"
}

You can setup more environments, check that section of .angular-cli.json and angular-cli repo.

Edit: As per your comment, you want more.

Yes but still this is not configurable after the build isn't it? Because I don't know what url the user want to use therefore I want to make it configurable from the outside after deploying the build.

Let's continue this scenario further. Let's have a backend client:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { apiUrl } from '../environment/environment.ts';
@Injectable()
export class BackendService {
    backendUrl: string = apiUrl;      

    constructor(private httpClient: HttpClient) {}

    get(endpoint: string, params: any): Observable<any> {
      const url= `${this.backendUrl}/${endpoint}`;
      return this.httpClient.get(url, params);
    }
}

Simplified, but works. By default, you set your own URL. But your components can set the url on the fly, and get other things from that url.

Now, the next step would be, offering which backends you have. This can be a preconfigured array, or you can let the client enter the url freely (simply input box). You can have a component that does that, and configures this service here. You should probably also have a separate service for your "proper" backend, where, e.g. your auth lies. But this all really depends on your scenario.

like image 21
Zlatko Avatar answered Oct 11 '22 13:10

Zlatko


  • Put your configurations in a ts file, in assets folder
  • just like you are fetching environment.ts, fetch that file and use its configurations
  • Contents of assets folder is not minimized, thus they can be configured in production build as well
like image 5
Akshay Vijay Jain Avatar answered Oct 11 '22 14:10

Akshay Vijay Jain


Do we really need APP_INITIALIZER to load dynamic environment variables

Well unless we need some async call to get them. Otherwise I would suggest this:

env.js

(function (window) {
  window._env = window._env || {};
  window._env.url= 'http://api-url.com';
}());

index.html

<head>
  <script src="env.js"></script>
</head>
<body>
  <app-root></app-root>
</body>

Finally add it in angular.json

"assets": [
              "any/env.js",

Now you can just read the window using a service in your app

like image 2
apenachi Avatar answered Oct 11 '22 13:10

apenachi