8

I have an angular 9 application in which I read the api url from an assets folder:

@Injectable()
export class ConfigService {

  private configUrl = '../../../assets/config/config.json';

  constructor(private loggerService: LoggerService) { }

  public async loadConfig(): Promise<any> {
    try {
      const response = await fetch(this.configUrl);

      if (!response.ok) {
        throw new Error(response.statusText);
      }

      return await response.json();
    } catch (err) {
      this.loggerService.error(`ConfigService 'loadConfig' threw an error on calling ${this.configUrl} : ${err.tostring()}`);
    }
  }
}

Method used for reading configuration file is described in Configuring angular production files after build.

environment.ts is

export const environment = {
  production: false,
  apiUrl: "https://localhost/api/",
};

environment.prod.ts is

export const environment = {
  production: true,
  apiUrl: "https://server/api/",
};

config.json is

{
  "apiUrl": "http://someTestServer/api/"
}

The incomplete script to copy apiUrl to config.json

var fs = require("fs");
fs.readFile(`./src/environments/environment.${process.env.CONFIG}.ts`, 'utf8', function (err, data) {

  fs.writeFile('./src/assets/config/config.json', data, function (err) {
    if (err) throw err;
    console.log('complete');
  });
});

My script section of package.json looks like following:

  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "build-test": "CONFIG=test node update-config.js && npm run build",
    "build-prod": "CONFIG=prod node update-config.js && npm run predeploy",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "predeploy": "ng build --prod",
    "deploy": "node ftpdeploy.js"
  }

Considering above: How can I automatically populate the contents of my config.json file based on different environment variable prior build so I don't need to manually copy and paste a json file to the \dist folder?

Update 1: Now I can copy the contents of my enviroment.xxx.ts into the config.json file. There is one problem remaining: When I copy the contents from environment.xxx.ts it copies the entire contents of environment.xxx.ts into the config.json (It also copies the import section of my enviroment.xxx.ts) however the expected result is to read the environment (export const environment) into an object and update the config.json according to the source environment object. How can I achieve this?

MHOOS
  • 5,146
  • 11
  • 39
  • 74
  • So you build your `config.json` from environment specific values from `environment.ts` and then modify some values? Why don't you use values from `environment.ts` build time values and values from `config.json` for dynamic values? – David Jun 18 '20 at 16:04
  • I use environment values for creating `default config.json files` which will get into an installer ready for distribution. I need to have two different builds, that’s why I put them in separate files. – MHOOS Jun 18 '20 at 21:31
  • Why don't you use both `environment`s and `config.json` for url configuration? You could provide default `url` via `environment` while enabling end user to change url changing property in `config.json`? – Sergey Jun 24 '20 at 14:54
  • @Sergey, User already can edit the config.json manually. The challenge is to copy default values from environment.xxx.ts file into config.json based on different build as described in the question. I want the default values to be copied into the config.json file automatically when I run something like `ng build --prod --configuration=xxx` – MHOOS Jun 24 '20 at 15:02
  • @MHOOS why isn't it suitable for you to leave this property empty in `config.json`? Thus in your config service you use url from `environment` if it's not provided via `config.json`. Seems easy and no need in extra manipulations with `config.json` – Sergey Jun 24 '20 at 15:04
  • @Sergey, Leaving the values empty inside the config is not an option since this application is a component of an `application suite` which gets added to an installer. In my case config.json values need to be populated. If automatic, much better for me, otherwise manually. – MHOOS Jun 24 '20 at 15:14
  • Why do you have import sections in your config file? If you populate your environment variable with some imported value, you won't be able to copy that to your json file – David Jun 24 '20 at 22:07
  • @David,I have to import some constant values from the core modules and the imports need to be there so there is no way I can get rid of them. – MHOOS Jun 24 '20 at 22:13
  • But you do not use these in your `environment` values, right? Because if you do, what you are trying to achieve won't work – David Jun 24 '20 at 22:20
  • @David, I do use those import values and that's the problem I am having right now: To extract some properties of environment object from environment.xxx.ts and push them into config.json file. – MHOOS Jun 24 '20 at 23:07

2 Answers2

9

Changing the pre-build script as:

const fs = require("fs");
fs.readFile(`/src/environments/${process.env.CONFIG}.ts`, (err, content) => {
  fs.writeFile("/src/assets/config/config.json", JSON.stringify(versionObject), () => { });
});

then from command line (I assume npm run build is the command to build, otherwise change it with your build command):

$ CONFIG=environment npm run build

should solve yuor problem.

Edit:

"scripts": {
  "ng": "ng",
  "start": "ng serve",
  "build": "ng build",
  // add more lines as you need like this, one for each build environment
  "build-test": "CONFIG=test node update-config.js && npm run build",
  "build-prod": "CONFIG=prod node update-config.js && npm run predeply",
  "test": "ng test",
  "lint": "ng lint",
  "e2e": "ng e2e",
  "predeploy": "ng build --prod",
  "deploy": "node ftpdeploy.js"
},

I noticed a \ in your question, probably you are running under Windows, make sure to use a bash: add a file called .npmrc with only following line script-shell=bash

Edit: if you want to read the environment file with fetch and parse it with await response.json(), the file should be a json file, not a ts file:

{
  production: true,
  apiUrl: "https://server/api/",
}

Hope this helps.

Daniele Ricci
  • 15,422
  • 1
  • 27
  • 55
  • I build the project using ng lint. That being said, Lets call above file config update-config.js. What changes do I need to make to my package.json to to automatically run update-config.js prior ng build --prod ? I added the script section of my package.json to the question, in case it helps. – MHOOS Jun 18 '20 at 21:34
  • I didnt manage to make this work. As you mentioned my enviroment is Windows. I created the the .npmrc file as well. In addition my script section matches yours. However when I run `npm run build-prod`, I get `'CONFIG' is not recognized as an internal or external command`. Clearly that Config is not defined. Now, where should I define this CONFIG in Windows command prompt? – MHOOS Jun 24 '20 at 13:54
  • @MHOOS `CONFIG=prod` is actually the point where bash initialize it. The error you got is the one we get when we are not using bash, so you need only to fix this. 1) make sure a bash is installed in your system (git for windows should install it) 2) if you have it, you can try to change the string `bash` in your `.npmrc` file with the full path of your bash.exe executable file 3) if still not working you can try this command `npm config set script-shell "C:\\Program Files\\git\\bin\\bash.exe"` with the actual path of your bash.exe executable file (note this is a global setting) 4) ask google;) – Daniele Ricci Jun 24 '20 at 15:14
  • You can try to add `utf8` as `encoding` argument both to `readFile` and `writeFile` functions. No, I didn't tested it – Daniele Ricci Jun 24 '20 at 16:58
  • I updated my question, you can check the progress. Now I manage to read the contents of environment but what I still don't know is how to ignore the import section and read only the environment object itself. – MHOOS Jun 24 '20 at 17:07
  • If you red it with fetch and than you want to JSON.parse it, it should be a pure JSON file, not a module (i.e. no `export` nor `const` keywords, no semicolon... ecc) – Daniele Ricci Jun 24 '20 at 17:18
  • Can you update your answer with your suggestion because I do not entirely get your suggestion. – MHOOS Jun 24 '20 at 17:22
2

From what you indicated in your updated posts and comments, what you want to achieve is not possible.

Basically, you want to copy an object (environment) value to a json file, but this object imports some of its values from somewere else

So you have something like this

somewhere.ts

export const constantValue = "myValue";

environment.xx.ts

import {constantValue} from "./somewhere";

export const environment = {
  production: false,
  apiUrl: "https://localhost/api/",
  otherValue: constantValue
};

Problem: before the build, you cannot access the value for constantValue, so you cannot really make it a prebuild script.

After the build, the environment object will be included in main-es2015XXX.js. However, the value for otherValue will be resolved only if you enabled buildOptimizer in your angular.json config, as shown below:

With buildOptimizer : false, the value is not resolved yet, it will be at run time. So you cannot use these values to write your json file. The environment variable will look like this:

const environment = {
  production: false,
  apiUrl: "https://localhost/api/",
  otherValue: _somewhere__WEBPACK_IMPORTED_MODULE_0__["constantValue"]
};

With buildOptimizer : true, the value for constants can be resolved at build time. The value wll look like this

const i={production:!1,apiUrl:"https://localhost/api/",otherValue:"myValue"}

However, the code in mainXXX.js is minified/uglified and I doubt that you'll be able to parse/extract the config value above...

So either remove all imports (use hard coded values) and use Daniele Ricci's answer, or manually create your json files.

Note

I did not fully understand your reason for doing that, but my feeling is that in your code you should use:

  • values from environment.ts for values that do not change after the build
  • values from config.json for values that can change after the build
David
  • 33,444
  • 11
  • 80
  • 118
  • your answer was quite insightful. The last two bullet points are the exact reasons for having hybrid configurations (Using both environment.xxx.ts and config.json). – MHOOS Jun 25 '20 at 13:50
  • Yes, that is what I would recommend doing – David Jun 25 '20 at 20:40