Angular-cli: Request: Method to pass environment variables during build vs file.

Created on 1 Feb 2017  ·  110Comments  ·  Source: angular/angular-cli

OS?

Any, mostly a build/CI question

Versions.

1.0.0-beta.26

Reasoning

I really like the environment variables setup with the CLI and we have switched to using them. One problem though is we no longer have access to server set ENV variables which is how we pass information to certain things during deployment. Consider:

Bugsnag.releaseStage = environment.releaseStage;
// can't access process.env anymore with the CLI
  const commit = process.env.CI_COMMIT_ID;
  if (commit) Bugsnag.metaData = {
        commits: {
          id: commit
        }
      };

Codeship ENV variables

Being able to add the release stage, branch, and commit ID allows us to segment and track bugs/issues very accurately.
My problem is I know the build takes whatever stage I pass and replaces the environment.ts file so I can't just edit the file with a bash script (well, if I know EXACTLY the build before hand I could) but it would be nice to be able to pass env variables in the build line.

Consider: ng build --prod could be: ng build --prod --envVar:commitId: CI_COMMIT_ID

which would append the variable after the file gets merged

export const environment =  {
    production: true, 
    commitId: 'ca82a6dff817ec66f44342007202690a93763949'
}

something like this would ensure it gets added to the right file and at the right time/place..

Then we could do:

  const commit = environment.commidId;
  if (commit) Bugsnag.metaData = {
        commits: {
          id: commit
        }
      };
devkibuild-angular feature

Most helpful comment

very needed for dockerizing the apps indeed

All 110 comments

@hansl we were just discussing this, thought you might have something to add

Why not make ENV variables available in process.env again?

I'm really feeling comfortable with Heroku Config Vars and Codeship environment variables, I already set up them, moved all my sensitive data like secret keys to angular-cli environment.dev.ts, BUT I can't use them.(

export const environment = {
  production: false,
  GOOGLE_RECAPTCHA_SITE_KEY: '6Le...Zq'
};

Because there is no way to access variables from test env or production like:

export const environment = {
  production: false,
  GOOGLE_RECAPTCHA_SITE_KEY: process.env.GOOGLE_RECAPTCHA_SITE_KEY
};

in my environment.test.ts and environment.prod.ts

Need to thinking on using third party packages like dotenv or env2

P.S: Is it hard to implement configuration like this:

...
"scripts": [],
"environments": {
        "source": "environments/environment.ts",
        "dev": "environments/environment.dev.ts",
        "test": "environments/environment.test.ts",
        "prod": "environments/environment.prod.ts"
      },
"processEnv": [
         "CUSTOM_ENV_VAR",
         "GOOGLE_RECAPTCHA_SITE_KEY",
         ...
]

So then there will be opportunity access them from process.env.CUSTOM_ENV_VAR, etc.

I ran into the same issue a day ago. And for a temporary workaround, that works (just checked it with Codeship), I just added the "prebuild" script, that generates the appropriate environment file using current env variables. ejs is used as a template processor due to it's simplicity.

scripts/prebuild.js - this is a script with the required logic

#!/usr/bin/env node

const fs = require('fs');
const path = require('path');

const ejs = require('ejs');

const environmentFilesDirectory = path.join(__dirname, '../src/environments');
const targetEnvironmentTemplateFileName = 'environment.prod.ts.template';
const targetEnvironmentFileName = 'environment.prod.ts';

// Define default values in case there are no defined ones,
// but you should define only non-crucial values here,
// because build should fail if you don't provide the correct values
// for your production environment
const defaultEnvValues = {
  PREFIX_STORAGE_TYPE: 'localStorage',
  PREFIX_USER_TOKEN_FIELD_NAME: 'userToken',
};

// Load template file
const environmentTemplate = fs.readFileSync(
  path.join(environmentFilesDirectory, targetEnvironmentTemplateFileName),
  {encoding: 'utf-8'}
);

// Generate output data
const output = ejs.render(environmentTemplate, Object.assign({}, defaultEnvValues, process.env));
// Write environment file
fs.writeFileSync(path.join(environmentFilesDirectory, targetEnvironmentFileName), output);

process.exit(0);

src/environments/environment.prod.ts.template - this is a template file, that generates the required environment file. Notice, that PREFIX_BACKEND_URL isn't provided in scripts/prebuild.js script in defaultEnvValues, because in production it should be defined only by build environment variables.

export const environment = {
  production: true,
  backendUrl: '<%= PREFIX_BACKEND_URL %>',
  storageType: '<%= PREFIX_STORAGE_TYPE %>',
  userTokenFieldName: '<%= PREFIX_USER_TOKEN_FIELD_NAME %>'
};

package.json - there are some changes here. In scripts/prebuild.js shebang (#!) style is used, so "prebuild" is just a pointer to the script file. And to make it work it must be marked as executable, that's why "postinstall" script is added either.

{
  ...
  "scripts": {
    ...
    "build": "ng build --prod --aot",
    "prebuild": "./scripts/prebuild.js",
    ...
    "postinstall": "chmod +x ./scripts/*.js"
  },
  ...
  "devDependencies": {
    ...
    "ejs": "^2.5.6",
    ...
  }
}

@k10der this is great, this is similar to what we did. What we do is actually move the environment scripting out of angular totally. We have a single environment.ts file for all builds and use a custom script to generate the correct values via the env variables.

I still think this should be something within the CLI but there are of course workarounds.

@DennisSmolek, I also think, that handling of process.env variables should be built-in in cli. And your approach to use a single environment file, that is generated by external tool makes sense: my dev environment variables are currently stored in the project repository, which is OK unless I'll start using some 3rd party APIs with private keys. So I guess one-environment-file approach should be used in future angular-cli versions.

Injecting environment variables during build seems essential. Any ETA for supporting this in the ng cli?

Why would common cross platform method for OS configuration support of env variables be excluded?

Unless there's a strong argument against 12factor that is being proposed for angular?

I'd also really like to be able to use process.env variables. I'm deploying to docker swarms and the capability to be able to include variables in a compose file is important to my application.

very needed for dockerizing the apps indeed

@k10der Do you have any repository on github that is using the solution you proposed? I'd like to check it because I could not find any real solution on the internet.

Hey, @carlosthe19916. I have a sample project, that I use just for practicing in Angular2+. It's not a production ready and you probably won't get any benefit from running it, but I use the proposed solution there. And it's exactly as I described it in this topic.

+1

Hi friends... What I think I want is to access environment variables at _run time_ (in addition to _build time_ as under discussion here.) For continuous integration purposes, I want to build only once, then deploy that same dist folder to my staging, qa, and prod environments using environment variables set with my CI provider on my runtime box. At first noodling process.env seems to equal {}. Do you feel I'm doing something wrong either technically or with the build-once-deploy-many approach?

@GaryB432

This is also my use case. I'm thinking to ignore the Angular CLI environments and use a plain old JavaScript include that sets my environment vars in a window.environment var.

Then, I can just deploy a different JavaScript include file and have different variable values with the same build output.

Workaround ahead. Might work fine in your setup, might not.

I've used two angular cli environments (dev and prod). Values that need to exists at build time are defined in environment.ts or environment.prod.ts. Values that need to be available during runtime are fetched from the window._env array in the environment.*.values.js file.

Production and Staging are using the same environment.prod.ts file, since this is used at build time, and you only want to do one build.

src/environment/environment.ts

export const environment = {
    production: false,
    backendUrl: (<any>window)._env.backendUrl,
};

src/environment/environment.prod.ts

export const environment = {
    production: true,
    backendUrl: (<any>window)._env.backendUrl,
};

src/environment/environment.values.js (development variables)

window._env = {
    backendUrl: 'https://localhost:7000',
};

src/environment/environment.prod.values.js (production variables)

window._env = {
    backendUrl: 'https://api.example.com/',
};

src/environment/environment.stag.values.js (staging variables)

window._env = {
    backendUrl: 'https://api-staging.example.com/',
};

Next, you need to make sure that you add this line to your index.html. We will place this file there during deployment.

...
<head>
  ...
  <script src="/assets/env.js"></script>
</head>
...

The tests also need to have these values, so I've loaded them at the top of the test.ts bootstrap file.

src/test.ts

// Load default environment values
import 'environments/environment.values.js';
...

Finally, you need to change your deployment/development scripts so that you execute this command:

For production:

cp src/environments/environment.prod.values.js dist/assets/env.js

For staging:

cp src/environments/environment.stag.values.js dist/assets/env.js

For development:

cp src/environments/environment.values.js dist/assets/env.js

I'm using Gitlab CI for deployment, and I execute this command before copying the dist/ to the production/staging server.
For local development, I'm using a Makefile to set everything up, so I execute the copy the file there right before I'm running the ng serve.

You might also want to add the dist/assets/env.js to your .gitignore.

How about the webpack.DefinePlugin mentioned in this answer? How do we integrate it into Angular CLI?

Another quick solution (any shell command in a template could break everything)

package.json:

"prebuild": "eval \"echo \\\"$(cat src/environments/environment.ts.template)\\\"\" > src/environments/environment.ts",

environment.ts.template:

export const environment = {
  production: false,
  clientId: \"${CLIENT_ID}\",
  apiId: \"${API_ID}\",
  authDiscoveryEndpoint: \"${AUTH_DISCOVERY_ENDPOINT}\"
};

Also, something like this could work better

perl -p -i -e 's/\$\{([^}]+)\}/defined $ENV{$1} ? $ENV{$1} : $&/eg' < src/environments/environment.ts.template | tee src/environments/environment.ts

My current workaround is a pre and post build event which replaces a version-tag in my environment.prod.ts file
export const environment = { production: true, system: 'prod', version: '%VERSION%' };

install npm-replace:
npm install replace --save-dev

package.json
"build:prod": "ng build --env=prod --output-hashing=all --output-path=dist/prod --aot", "prebuild:prod": "replace '%VERSION%' $VERSION src/environments/environment.prod.ts", "postbuild:prod": "replace $VERSION '%VERSION%' src/environments/environment.prod.ts",

Jenkins runs it with: ($Tag is my release-tag, for example "1.0.0")
VERSION="$Tag" npm run build:prod

We wound up sing this Visual Studio Team Services extension in our release. Very happy with it.

Adding my voice to this request--in the middle of doing DevOps work on three different Angular 4 applications and the lack of access to environment variables has made this much harder than it needs to be. I'm hopeful this can be rectified soon.

I agree with @GaryB432 that access at both build and runtime would be great, but I could settle with build time if need be.

To add to this, on Heroku I currently need to commit new code to change the env variables. Having access to process.env would let us change the environment variables through the browser and command line without needing to commit anything.

If anyone is interested we made a little command line util to add to your CD step. It takes an EJS template and processes with process.env variables context. We use it to grab our build provider's environment vars and stick them into a small global object script which is then loaded into index.html

Just adding to my comment above, if you guys are using Azure we created a build/release step that plugs environment variables into a small shim script. Very simple and meets our requirement, viz: build one dist folder and deploy it to multiple environments. I'm sure the underlying package on which it is based, the one I mentioned in the previous comment, is adaptable to other CI/CD ecosystems.

This CLI approach generates a file that doesn't messes up with the version control and keeps them on environment.ts

are there any news? it would be nice to have this feature, I guess there are plenty of user's cases

i'm interested in this for post-build, like @GaryB432 mentioned, build once/deploy many. I'm not running on azure so need to plug the vars during deployment from vsts Release. CI Process builds creates artifacts from dist folder. I suppose @michaelarnauts way would work for me. What are the drawbacks to having variables in plain js from assets/env.js? At least when webpack runs on the files now its garbled a bit to find the environment const.

@k10der, looking at your guidance. Could you please reply to the following if possible?

1) In which order do we need to execute the npm scripts within a CI environment i.e. Azure CI. Something like below PS: buildsuite?
`"scripts": {
"build": "ng build --prod",
"prebuild": "./scripts/prebuild.js",
"postinstall": "chmod +x ./scripts/*.js"
"buildsuite": "npm run postinstall && npm run prebuild && npm run build"
}

2) If have multiple env variables defined in Azure CI app settings then what the following line of code will do?
const output = ejs.render(environmentTemplate, Object.assign({}, defaultEnvValues, process.env));
Thanks - Ravindra.

Hey, @ravivit9. I'm not aware about Azure CI, but I guess it's using Windows. So the scripts I shared earlier won't fully work.

Usually you define build steps in CI/CD service, so I don't see a need to create a separate buildsuite script. Also this buildsuite script doesn't give you any value, because npm run postinstall is automatically run after you run (or CI/CD server executes) npm install. Same thing for npm run prebuild - it's run before you run (or CI/CD server executes) npm build.

So taking this into account you need to:

  1. Update the ./scripts/prebuild.js file by removing the first line of code (#!/usr/bin/env node).
  2. Update prebuild script definition in package.json (remove postinstall script, since it won't work on Windows; change prebuild; the buildsuite is actually not required at all).
  "scripts": {
    "build": "ng build --prod",
    "prebuild": "node ./scripts/prebuild.js"
  }
  1. Create tasks in your CI/CD service:

    • Install dependencies (run npm install)

    • Run tests (if you have any)

    • Build the project (run npm run build)

    • Do something with the new build artifacts

Regarding your second question.

const output = ejs.render(environmentTemplate, Object.assign({}, defaultEnvValues, process.env));

It creates a constant (output), that will contain the result of the template rendering (ejs.render(environmentTemplate, ...)), and that template (environmentTemplate) will take it's values from an object, that is generated from default values (detaulfEnvValues) and your current environment variables, that you define in your CI/CD (process.env).

Thanks for getting back to me so quickly.

As per step 3, when does "prebuild" task will be run if I don't run. How and where will I invoke prebuild before ng build.

My requirement is I have four different environment.<>.ts files under environments directory
environment.dev.ts
environment.ci.ts
environment.tes.ts
environment.uat.ts
environment.prod.ts

So the prebuild script will literally read one of the above field based on process.env.CURRENT_ENVIRONMENT value and overwrite all contents of environment.ts file.

package.json

scripts: { "build": "ng build", "prebuild": "node ./server/prebuild.js", "buildsuite": "npm run prebuild && npm run build" }

In Azure CI the command after npm install will be npm run buildsuite to ensure to run prebuild first and then build.

scripts/prebuild.js

`const fs = require('fs');
const env = process.env;
const targetEnvFileObj = {};
targetEnvFileObj.dev = 'environment.dev.ts';
targetEnvFileObj.ci = 'environment.ci.ts';
targetEnvFileObj.test = 'environment.test.ts';
targetEnvFileObj.prod = 'environment.prod.ts';

const currentEnvironment = process.env.CURRENT_ENVIRONMENT; // possible values ci /test/uat/prod

function replaceContents(sourceEnvFile, cb) {
fs.readFile(sourceEnvFile, (err, contents) => {
if (err) return cb(err);
fs.writeFile('environment.ts', contents, cb);
});
}

replaceContents(targetEnvFileObj[currentEnvironment] , err => {
if (err) {
// handle errors here
throw err;
}
console.log('done');
});
`
Appreciate your view on this.

@ravivit9

As per step 3, when does "prebuild" task will be run if I don't run. How and where will I invoke prebuild before ng build.

You can define pre and post scripts for any npm script, so prebuild will be invoked by npm before build script when you run npm run build (https://docs.npmjs.com/misc/scripts#description - the last paragraph in the Description section).

And regarding your question about how to best implement environment changing. In this topic users discuss how to provide custom variable to the resulting file. And it seems you have all the files pre-defined. So you need just to configure a command in CI, that will take environment variable and pass it's value to ng build --prod --environment=$ENVIRONMENT_NAME command or something like this (https://github.com/angular/angular-cli/wiki/build#build-targets-and-environment-files).
And I guess your type of question fits better for StackOverflow.

@nhammerl Can you give a little insight into your approach. I have the same use case where the backend services URL can differ. What is the best approach to handle this, without having to create multiple env.js files. Isn't there any way I can mention the URL (variable value) from command line?

Hi @k10der, Thanks for a reply.

I am aware of ng build --env=test/ci/... but my use case is build once deploy multiple environment. I am not sure whether this is possible with angular-cli at the moment. My CI environment is Azure where env variables are declared in Azure app settings per environment. Currently one build done and run a node server to monkey patch the env variable in ./bundle.js, but that is not an efficient way of building for different environment.

Even with the above temporary solution it may not going to work as the build is going to happen only once whichever environment it's going to build but the same build is going to be picked up and deployed in different environments like ci, test, uat and prod.

Any suggestion for one build multiple deployment in CI?

I'm not sure it's possible (not only for angular-cli, but also for angular itself), because( if I'm not mistaken) environment files are injected and used in project compilation phase by angular compiler, so in order to provide different values, you probably need to build the project once again.

And I recommend you to ask this question on StackOverflow, because your question is more about CI/CD and deployment process, which isn't a quite fit for this topic.

We are using my method in production fine.

Basicly, during deployment, I add the corresponding .js file to the build output that puts the variables on a window._env object. This file needs to be added to the html.
Then, in the usual Angular environment files (src/environment/environment.prod.ts), I reference the values with (<any>window)._env.backendUrl.

You just need add place the environment-dependant .js file to the build output, and the app will be configured differently.

We consider this to be a mandatory basic feature that should be included in Angular.
Our team is in the process of containerizing the app using Jenkins, Docker and Kubernetes.
We need to pass in many external variables to the angular app using Jenkins builds but are having to use workarounds to achieve such a simple task.

Definitely having access to process.env variables is a primary must for our development.

Reported almost a year ago and still no straight forward solution :-(

Yeah, when you use docker, Being able to change environment variable in run time becomes essential. For now, we have to call backend service to fetch the config which is too much work.

Just a simple question

given a web page at x.com/index.html that is servicing the user in an excellen way, it was added an add-on sweet, an angular2/4/5 app, loaded from y.org/ via y.org/y.js and injected into some div tag in the mentioned index.html, how can the angular app be told it's base url (=y.org not x.com) ?

kind regards

@samyue By “run time” you mean “compile time” right?

yes, sure,
a plain html page composing kind of web parts out of angular components from variety of origins.

an ng build option for pressing some value(s) into placeholders in environment.prod.ts, or pressing them just before deployment.

There are few use cases for this feature. It would be really nice to have it built in.

Anyway, here is one solution I am using in my projects.

https://gist.github.com/nsmgr8/c4411391918c773447497af01533dd78

@amcdnl just wrote an awesome blog post on this which IMO is the best way to do this for now, https://medium.com/@amcdnl/version-stamping-your-app-with-the-angular-cli-d563284bb94d

It seems like this is a lot of extra work for what should be just token replacement in a single configuration file. I don't really get the point of having different files for different environments. I get having a function call to disable or enable the particular mode an application is running (to enable additional diagnostics or whatever), but structuring environments in different files makes it more difficult to have artifacts that go through testing in different stages. I want to cookie cutter it and deploy it into the next environment.

I would like to be able have a single environment configuration file that has tokens in place of the values, and during the build, bundle my application scripts. I'd like, at deployment time, to take the bundled file and replace the tokens with variable values. And depending on the environment I'm going to deploy to, flag it as production and decide to minify the script, then deploy.

Not all deployments involve building from the target system the application will wind up executing on. In a CD setup, like one within VSTS, a build agent creates deployable artifacts that are used by release management definitions. Those release definitions specify environments and conditions for deploying to each. For each of those environments, it executes tasks from a remote agent.

If I want to add a new environment and deploy a build from last week, I shouldn't have to make a code change to introduce a new environment. That shouldn't be my application's concern. The tokenized configuration file should remain the same in that case. I just need my foot in the door during build to be able to have a single configuration that I can transform myself after being bundled, not per environment configuration TypeScript files. Then let me optimize the JavaScript bundle and generate maps, etc. at deployment time without having to having to keep the TypeScript around and pull in 3rd party dependencies again.

This isn't limited to CD, because I could easily have in my local development environment some scripting during the build that would replace the tokens with values for my local environment. This is really a concern with having environment files for configuration.

It's quite bizarre that the CLI team hasn't exposed some way to do simple token replacement in environment.ts. Forcing developers to have separate environment files for every possible combination of values is untenable. This seems like a no-brainer, must-have feature, and one that would be easy for them to add as well. Can someone from the CLI team offer some insight on if/when this is planned?

I read the main question from DennisSmolek and all the comments.

In our Angular projects we use a Task Runner as PRE-PROCESSOR before running angular-cli to build the App.

I think the Angular team has other priorites and has to concentrate efforts in the Angular core development, not changing the way Angular-cli builds the App according to complex logic using environment variables. It may open new issues and possibilities for another requests like this.

You can use any Task Runner (Gulp, Grunt, Gradle, Ant, Shell Scripts, etc.) to PRE-PROCESSING your .ts files, do string replacement, change the App build number and so on, before running Angular-ClI.

The task runner is really important for Jenkins and CI / CD.

@jocafi I appreciate your perspective and opinion, but please let the Angular team speak for the Angular team.

A pre-processor will not work, because the build server has no clue what environment it’s going to deploy to until it’s going to be deployed. I can’t update the values in typescript files, because I don’t have those values at compile time.

I also don’t want to build it again, because if I have a 3rd party dependency that has a version “latest,” it will pull in the npm package that is latest at the time it is built.

If that build goes into UAT or another environment, is tested, signed off on, I need to deploy that same compiled version at the time it went into testing. A new build could get a later version of a dependency, and thus present issues not surfaced during testing.

By time a deployment happens, or could be days or weeks after it was built.

I probably have a workaround of creating an environment.local.ts without tokenized values, environment.ts with tokenized values, and a duplicated environment.prod.ts with the same tokenized values, but production being set to true.

This would require me to build both a dev build that will be used for anything that needs to be debug, and a production build and use it for anything that doesn’t, like staging and performance testing, and production, but doing a production build minifies it and can be problematic with token replacement.

So I’ll probably wind up using the dev build for everything, and then minify it by some other means.

I still don’t solve the problem about a bunch of other flags that have been already set based on if it was a prod build or not at build time. I can’t modify those after they were bundled, it would require running angular-cli again.

So I’m back to where I started.

Until this is addressed, I've adopted a solution like this: https://medium.com/@natchiketa/angular-cli-and-os-environment-variables-4cfa3b849659

Basically a separate npm script that is run before whichever main ng command and generates the relevant environment file. It's a total pain but it works.

FWIW @brian428 We do something similar.. Our CI allows us to configure our own build pipeline, so we run our shell script just before the angular CLI, essentially bypassing the cli's own env.X.ts logic to a single file.

You can do this with NPM (like you linked) with pre-post builds (for our use cases at least) and it seems to work. Feels super hacky though.

I had the same problem, I wanted to dynamically choose which mongodb I used (localhost or a test db on mLab). I found a workaround but would like to know if this is sensible or if it could be done better..

npm script sets a node enviroment variable:
cross-env USE_LIVE_DB=true doSomeOtherStuff

Then in my index.js I conditionally load the db uri using the dotenv package:

    if (process.env.USE_LIVE_DB) {
      require('dotenv').load();
    }

Then I can set my MONGODB_URI variable in a .env file then connect to my database like:

    var mongoDB = process.env.MONGODB_URI || 'mongodb://localhost/db';
    var mongoose = require('mongoose');
    mongoose.connect(mongoDB);

Seems to work and was fairly easy to set up, but i'm just a hobbyist so is this a good/efficient/secure way of doing things (this is obviously just for test purposes, I wouldn't have two databases like this for production).

@michaelb-01 Here is Angular, a framework for browser. You are talking about server-side code like Express.

@franklinyu your point is invalid. We dont talk about changing configuration at runtime - we talk about the build process.

@ruffiem Yes, we are talking about the build process of front-end code. I’m not sure whether @michaelb-01 is talking about front-end code, since he mentioned MongoDB. How does that make my point invalid?

I wonder if we might affect environments via schematics (Angular v6 and higher), by rewriting environment file before build and restoring it afterwards. It still counts as preprocessing, like some of the solutions posted above, but made by Angular tools.

Using schematics really seems like overkill. There are plenty of existing NPM libs that can perform simple token replacement in a file. That's literally all that's needed here.

@k10der hi i am trying to go by your approach . Can't we put const defaultEnvValues = {
CA_BACKEND_URL: ${process.env.BASE_IP},
};

Also, can you please explain little more about how this script will provide the environment at runtime, because for me its not providing i know i am doing something wrong

to start my application : i am doing

ng serve

and to build i am using

ng build --prod command.

But, the default value is not getting injected into the build

@rrohitesh I guess you didn't get the method I proposed: environment variables get rendered to the environment file while you start dev server/build the app via npm scripts (here's more info about them https://docs.npmjs.com/misc/scripts). So running ng serve or ng build won't do anything, because environment rendering logic isn't processed via rendering script, because it's not called. Please read the method description more properly - all the required info is there.

And the purpose of defaultEnvValues is to provide some reasonable values for cases when no corresponding environment variable is set, so setting default value to an environment value, that may not exist is irrational.

A valid workaround I haven't heard anyone else mention here until/unless something like https://github.com/angular/angular-cli/issues/7507 gets implemented is to switch out the traditional "SystemJS approach" build process with webpack and then use the webpack environment plugin.

@dtzar Yes, that's the best solution in my opinion.

Actually I said that about a year ago in https://github.com/angular/angular-cli/issues/4318#issuecomment-312916291, but no one seems to care. Also, the DotenvPlugin looks very cool. This way it's twelve factor, and basically every backend framework (Rails, Django, etc.) has already been doing this.

However I don't think it is possible for this workaround to work for now, given that configuration details of Webpack is hidden behind Angular CLI.

@franklinyu if you look at the link I provided with webpack, it seems you can use _just_ a regular webpack configuration file exactly how webpack works if you switch over your angular build system away from the SystemJS approach. I haven't tested this configuration with Angular, but I do know the webpack environment plugin works great with React.JS projects I'm working with.

@dtzar You mean using Webpack directly, without Angular CLI?

I'd like to use environment variables like this:

export const environment = {
  production: false,
  access_token: process.env.ACCESS_TOKEN
};

Consider combine/merge process.env with environment.ts file?

But if do this, I think the environment.ts file is useless. Use process.env variables directly is good enough.

https://stackoverflow.com/questions/51489904/angular-6-should-i-put-secret-environment-variables-in-environment-ts-file

For anyone still looking at this and using Azure...

You can actually just use the Application Settings within the Azure portal to set your process variables there. You don't need to do it during the build process.

https://docs.microsoft.com/en-us/azure/app-service/web-sites-configure#howtochangeconfig

I completely ignored this obvious route for keeping secret keys because I didn't think there's any way Microsoft would ever support this with node. But, surprisingly it works like a charm.

All the variables you set in the app settings can be referenced in your node app by process.env.*

@mrdulin How do you use process.env in Angular? Will browser recognize process?

@franklinyu

Sorry. I am still blocking here.

Here is my stackoverflow post: https://stackoverflow.com/questions/51489904/angular-6-should-i-put-secret-environment-variables-in-environment-ts-file

P.S. Using webpack or some magic scripts to generate, inject the environment variables. Of course it can work. But I think angular should provide a more simple way.

@mrdulin I think smnbbrv's answer (in the question you linked) is pretty good and sums up all the options you have. Any issue with that answer?

My current workaround is a pre and post build event which replaces a version-tag in my environment.prod.ts file
export const environment = { production: true, system: 'prod', version: '%VERSION%' };

install npm-replace:
npm install replace --save-dev

package.json
"build:prod": "ng build --env=prod --output-hashing=all --output-path=dist/prod --aot", "prebuild:prod": "replace '%VERSION%' $VERSION src/environments/environment.prod.ts", "postbuild:prod": "replace $VERSION '%VERSION%' src/environments/environment.prod.ts",

Jenkins runs it with: ($Tag is my release-tag, for example "1.0.0")
VERSION="$Tag" npm run build:prod

I tried this and running it locally it says: The term 'VERSION=1.0.0' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

Has anybody ran into this issue?

@Mateopc12 What operating system are you using? The VERSION=1.0.0 cmd syntax only works in Bash-like environment.

@FranklinYu How about this CLI tool which reads variables from process.env or other JSON files and writes them into environment.ts.
It has been forked, starred and downloaded several times

@kopz9999 Thank you for writing this tool. We have several unofficial work-around mentioned above; most of them are just one more command, so it's like node my-script.js && ng build. Nevertheless it would be great for Angular CLI to include this function. This is a very common pattern (most SPA needs to connect to backend, whose URL shouldn't be hardcoded in codebase), and tools should be optimized for common patterns. React team has it right, why can't Angular team? I would have submitted a Pull Request if I found where to insert the logic.

This depends a bit on your use case. If you know your configuration at build time, the tool provided by @kopz9999 is the correct solution (or #12810 if I can find the time).
If you need to configure your angular app at runtime/on startup, something like angular-server-side-configuration might be a better fit. (Disclaimer: I am the author of this tool.)

You can create the environment.ts file at building time.

With this solution you are basically reading a .env file and then creating the environment.ts file accordingly with the information taken from the .env.

At this point all the "magic" is done by the following set-env.ts file (that you should create in the root folder) that is actually creating the environment.ts file:

import { writeFile } from 'fs';

// Configure Angular `environment.ts` file path
const targetPath = `./src/environments/environment.ts`;

// Load node modules
const colors = require('colors');
require('dotenv').load();

// Debug environment variables

// `environment.ts` file structure that uses the environment variables
const envConfigFile = `export const environment = {
    apiBaseUrl: '${process.env.API_BASE_URL}',
    apiUrl: '${process.env.API_URL}',
    appSecretKey: '${process.env.APP_KEY}',
    appName: '${process.env.APP_NAME}',
    awsPubKey: '${process.env.AWSAPIKEY}',
    nodeEnv: '${process.env.NODE_ENV}',
    production: '${process.env.PRODUCTION}'
};
`;

console.log(colors.magenta('The file `environment.ts` will be written with the following content: \n'));
console.log(colors.grey(envConfigFile));

writeFile(targetPath, envConfigFile, function (err) {
    if (err) {
        throw console.error(err);
    } else {
        console.log(colors.magenta(`Angular environment.ts file generated correctly at ${targetPath} \n`));
    }
});

Apart the colours used for the logs (by colors module, to have a bit of debug of what will be written in the environment.ts), the concept is pretty clear: we are accessing the process.env variable made available by Node, and creating/overwriting the environment.ts file that contains the environment object as in any Angular project.

At this point you just need to add (or change) the scripts node inside your package.json so you can start this file before the build, something like that:

{
    ...
    "scripts": {
        "ng": "ng",
        "config": "ts-node set-env.ts",
        "start": "npm run config && node server",
        "build": "npm run config && ng build",
        "test": "npm run config && ng test",
        "lint": "ng lint",
        "e2e": "ng e2e",
        "server": "npm run config && ng serve --port $PORT",
        "postinstall": "npm run build"
    },
    "dependencies": {
        ...
        "colors": "^1.1.2",
        "dotenv": "^5.0.0",
        ...
    },
    "devDependencies": {
        ...
        "ts-node": "~5.0.1",
        ...
    }
    ...
}

Where dotenv is used to read the .env file (some cloud systems require it in the dependencies node, else you can put it in the devDependencies); ts-node is a of Node wrapper for TypeScript, it is used simply to run the set-env.ts that we created earlier as this is a TypeScript file (I prefer this way because it is maintaining consistency in the language used by Angular, but you can easily transform this file into a JavaScript one and run it via Node and forget about this dependency).

At this point you just need to run the npm scripts instead of the normal Angular-CLI commands, or remember to run the “magic” file first to create/change the environment.ts file if you have changed some configuration in your development.

@Ferie Yeah. The way you provide is an option. But, it will be more cost for writing script(.sh, .js or others) and combine them together. Yeah, the scripts can do anything. But it's not easy for a beginner.

I think the framework should be out of the box. Why don't angular team make it too complex? Yeah, I am saying the @angular/compiler module. It wraps webpack compile and bundle tool inside.

I think to keep the process shim for the browser environment is easy and good enough to use.
And, give developer the control to writing their custom webpack config is good enough. Sometimes the developer need modify every webpack config includes: loaders, plugins and the config for each of them to satisfy the requirements.

TL;DR;
hi NG Team,
ng Team this is a cool Feature please Provide it.
I have to provide few environment variable to the app like spring-boot allows to override its known variable or app variable user can also provide it.
Not asking right now for different profiles and other stuff.

Hello,

I also need this feature on my project.
I have to stop using fileReplacements in angular.json for now because of that, it's too bad.

Thanks for the work!

Thinking this might be a decent workaround for azure builds

https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens&targetId=aed09b2f-5590-474a-81a6-e76749731918

This is what I use during build pipeline. The non-default environment.*.ts contain the replace tokens,the replace tokens tasks injects the variables then angular file replacements replaces environment.ts with the resulting file

hello,

The flag "need: further discussion" has been removed.

Has something been decided concerning this feature?

Thanks.

If you add a custom webpack config you can regain access to process.env and thus access system/pipeline variables.

This article helped me.
https://blog.usejournal.com/system-environment-variables-in-angular-1f4a922c7b4c

@titanium170 Yes. webpack and shell, nodejs script are really powerful. Almost can do anything, but just for injecting env vars at runtime of angular application, seriously? I think angular should provide an out of the box

@titanium170 Yes. webpack and shell, nodejs script are really powerful. Almost can do anything, but just for injecting env vars at runtime of angular application, seriously? I think angular should provide an out of the box

@mrdulin I agree that this should be more out-of-the-box, however, I think since this option is available for little effort it is a good solution until the Angular team decide to do something about this. Angular is built using these technologies so I see no reason to not make use of them in the meantime.

I just use a token replace script ... simple enough🤷‍♂️

@titanium170 Yes. Do you mean we have to write custom webpack config instead of using the config angular framework provides? If it's true, it's easy for you, for me, but for a beginner? I don't think so.

@ricardosaracino 😄Can you share it with us? A shim script?

@ricardosaracino Agreed… @mrdulin You can use sed or something like this https://www.npmjs.com/package/replace-in-file as part of your build process.

@peschee I care about how to inject env vars at runtime, not build process. There is a way like @titanium170 said, is to using custom webpack config and plugins so that I can inject env vars into process.env at runtime. Why process.env? Because https://github.com/angular/angular-cli/issues/4318#issuecomment-405194108

if you are serving the site with apache, or IIS there is no process.env, i dont think this makes much sense in terms of being in angluar

This is actually beyond the web server used. A browser does not have environment variables. process itself is a Node.js specific API and also does not exist in a browser. While both the browser and Node.js use JavaScript, they are not the same platform. Each has a differing set of APIs and features.

@mrdulin Maybe something like this might fit your use case? You can add it by executing ng add angular-server-side-configuration@next and see the documentation on how to use it at runtime. (Disclaimer: As stated before, I am the author of that package.)

While I'd like to see environment variables get first class support in the CLI, the angular-builders project makes it really easy to add support yourself.

Example webpack config (webpack.config.js) I've been using:

const Dotenv = require('dotenv-webpack');

module.exports = {
  plugins: [
    new Dotenv()
  ]
};

You'll then need to modify the angular.json file as mentioned in the angular builder docs.

My original issue at https://github.com/angular/angular-cli/issues/7507#event-2677531132 was closed in favor of this one.

From a 12 Factors perspective, dotenv is, strictly speaking, the anti-pattern of writing config to files. see: https://12factor.net/config

The EnvrionmentPlugin for webpack is already included in production builds. The EnvironmentPlugin could be in all builds and .angular-cli.json could be extended to include defaults for your project.

This approach doesn't add new plugins, only requires minimal changes to the current build process, and keeps all the config in .angular-cli.json.

Some kind of workaround if you are using some sensitive data that you shouldn't commit, I don't know if it has been suggested before, I try to read the whole thread but it's from 2016, for code sake.

Create a typescript file, exporting the data you need, and don't commit that file.

I know it's not as simple as having access to process.env, but anyway most people here are requesting this feature to have a .env file, so kind of the same.

I have my firebase configuration as:

|- environment.ts
|- environment.prod.ts
|- environment.firebase.ts

Both environment and environment.prod reexport the firebase config file as its content won't change.

From a 12 Factors perspective, dotenv is, strictly speaking, the anti-pattern of writing config to files.

I agree. We use nconf instead which allows us to use hierarchical configuration: e.g. we define default values in env-files and override what's needed via env-vars.

+1 Any update from the Angular team on this one?

@clydin to evaluate some possible designs and come up with a proposal.

I have used this approach of loading a local json file ... it works well.

import { NgModule, APP_INITIALIZER } from '@angular/core';
import { AppConfigService } from './app-config.service';

@NgModule({
  ...
  providers: [
    AppConfigService,
    {
      provide: APP_INITIALIZER,
      useFactory: appInitializerFn,
      multi: true,
      deps: [AppConfigService]
    }
  ],
  ...
})
export class AppModule { }

https://juristr.com/blog/2018/01/ng-app-runtime-config/

I have used this approach of loading a local json file ... it works well.

import { NgModule, APP_INITIALIZER } from '@angular/core';
import { AppConfigService } from './app-config.service';

@NgModule({
  ...
  providers: [
    AppConfigService,
    {
      provide: APP_INITIALIZER,
      useFactory: appInitializerFn,
      multi: true,
      deps: [AppConfigService]
    }
  ],
  ...
})
export class AppModule { }

https://juristr.com/blog/2018/01/ng-app-runtime-config/

This works but its for runtime.
The ticket above is for build-time configuration.
Like passing process.env.<ENV_VARIABLE>

This is supported very easily with webpack i dont see it being so hard to implement

This works but its for runtime.
The ticket above is for build-time configuration.
Like passing process.env.<ENV_VARIABLE>

This is supported very easily with webpack i dont see it being so hard to implement

I know... just thought someone might stumble across this post and need a solution

A humble summary of available ways to manage app state from command line.

Whether angular app is running on server, it is possible to convert env variables to angular providers as noted here https://angular.io/guide/universal which is architecturally a cleaner way imo:

server.get('*', (req, res) => {
  res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});

As for common builder way there is an option to provide different configuration (https://angular.io/guide/workspace-config#alternate-build-configurations) that in turn can affect environment file or any other build artifact, but in enumish way: one cannot provide some custom values but instead choose of any predefined ones.

I use Azure build pipe lines to build artifacts and to deploy to on premise servers. Testing a build on QA and then rebuilding for Production is not good practice.. The QA tested build should be promoted.

A humble summary of available ways to manage app state from command line.

Whether angular app is running on server, it is possible to convert env variables to angular providers as noted here https://angular.io/guide/universal which is architecturally a cleaner way imo:

server.get('*', (req, res) => {
  res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});

As for common builder way there is an option to provide different configuration (https://angular.io/guide/workspace-config#alternate-build-configurations) that in turn can affect environment file or any other build artifact, but in enumish way: one cannot provide some custom values but instead choose of any predefined ones.

I dont like angular's env.<type> configuration solution.
It reminds me of the olf school browser-detection instead of feature-detection.

Essentially if i need all configs to be the same except 1 i need to create a whole new configuration file? Add that to my angular.json? Recommit my code, get PR reviewed, Update my pipelines... its too much.

DYNAMIC_ENV=hello ng build i should be able to just do that. no one else needs to change.

And if my devops guy needs to test a new environment with different configurations i dont need to be involved at all.

If you are doing CI/CD in azure (for example) you can use Tokenizer to replace placeholders

here is an example of a CI replacement .. not elegant but its clean

image

To differentiate, because a few different issues seem to be conflated here.

Build-Time Configuration
Currently available:

  • Replace files for different builds (with fileReplacements)

Desired (As stated in the feature request of this issue, if I understand it correctly):

  • Dynamically change configuration based on environment variables or cli parameters

Is @clydin going to (only) focus on this? Will #3855 be taken into account?

Workarounds:

  • Dynamically create environment.prod.ts (or similar) before calling ng build --prod
  • Create a custom builder which dynamically creates/replaces the configuration in the host, or taps into the webpack configuration

Startup/Runtime Configuration
Not supported by Angular CLI.

Is this out of scope for @clydin? Should a separate issue be created for this?

Possible solutions:

  • Configure an Angular app with environment variables on container startup with angular-server-side-configuration (of which I am the author)
  • Use an approach as described previously, by loading a JSON file or response on loading the app in the browser (e.g. https://github.com/angular/angular-cli/issues/4318#issuecomment-618533387)

How can I get env variable from heroku env and use them in my code? I was searching and did not get any solution :(

I have been using venv, a pretty simple solution that works with angular or with any js app.

Dear Angular Developers,

Passing environment variables should be a no brainer basic feature of any
build system. Without it, we are left to use third party tools (e.g.
Tokenizer) or if these tools are not available, create our own function to
find and replace key,value pairs within the environment.ts file; thereby
making temporary changes to build source code.

A better approach would be to have Angular accept environment variables or
even pass them in the command line. This would enable build developers
to write polymorphic build scripts
that will set up and do the right thing
for the environment being built for. Examples where this is important are:

  • building for a local Dev or QA environment on any OS, Container, etc.
  • building for a shared Dev, Staged environment on any OS, Container,
    Pod, etc.
  • building for a Production environment on any OS, Container, Pod, etc.

Values that would be easier to pass into Angular via environment variables:

  • IP Address and port of service
  • IP Addresses and ports of services being dependent upon
  • Location of TLS Certificates

What is the difficulty here? Do you need assistance with making this
happen?

Kind regards,
Ralph

Ralph A. Navarro Jr.
Navarro Computing LLC https://navarrocomputing.com
Mobile: +1(508)287-0190
Skype: eaglet3d

On Tue, Apr 28, 2020 at 8:49 AM Gary Bortosky notifications@github.com
wrote:

I have been using venv https://www.npmjs.com/package/venv, a pretty
simple solution that works with angular or with any js app.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/angular/angular-cli/issues/4318#issuecomment-620585208,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AA2VJIV7ENQU34KSDWMBLITRO3GGDANCNFSM4C6P2MVA
.

I used a trick to set the NODE_ENV variable manually during build.

I'm using a custom webpack file to be able to use Tailwind CSS with Angular.
I also need to control the size of my final style sheets, so I use the PurgeCSS feature from Tailwind. To be able to work automatically, the plugin need the NODE_ENV variable set to production.
I inserted this line at the start of the webpack file.

process.env['NODE_ENV'] = process.argv.indexOf('--prod') !== -1
  ? 'production'
  : '';

Then, when the production build starts with ng build --prod, webpack recognize the environment as production, and the PurgeCSS script is executed.

Definitely not ideal but it work for the time being (with 9.1.1).

Dear Angular Developers, Passing environment variables should be a no brainer basic feature of any build system. Without it, we are left to use third party tools (e.g. Tokenizer) or if these tools are not available, create our own function to find and replace key,value pairs within the environment.ts file; thereby making temporary changes to build source code. A better approach would be to have Angular accept environment variables or even pass them in the command line. This would enable build developers to write polymorphic build scripts that will set up and do the right thing for the environment being built for. Examples where this is important are: - building for a local Dev or QA environment on any OS, Container, etc. - building for a shared Dev, Staged environment on any OS, Container, Pod, etc. - building for a Production environment on any OS, Container, Pod, etc. Values that would be easier to pass into Angular via environment variables: - IP Address and port of service - IP Addresses and ports of services being dependent upon - Location of TLS Certificates What is the difficulty here? Do you need assistance with making this happen? Kind regards, Ralph Ralph A. Navarro Jr. Navarro Computing LLC https://navarrocomputing.com Mobile: +1(508)287-0190 Skype: eaglet3d

On Tue, Apr 28, 2020 at 8:49 AM Gary Bortosky
@.*> wrote: I have been using venv https://www.npmjs.com/package/venv, a pretty simple solution that works with angular or with any js app. — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub <#4318 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA2VJIV7ENQU34KSDWMBLITRO3GGDANCNFSM4C6P2MVA .

Not defending the angular team here as this definitely would be a useful feature, however almost all projects I've worked on just hit /api. Seems to me all your issues could be addressed by just deploying your frontend along side your backend, and you avoid CORS issues and many other problems. Of course this only applies if you want a backend (or at least a basic nginx, which I'm guessing you'd need for 404/reload handling anyways)?

When you need to embed, for example the commit hash, as the example provided by @DennisSmolek. I went around this as follows:

git-version.js

const { gitDescribeSync } = require('git-describe');
const { writeFileSync } = require('fs');

const gitInfo = gitDescribeSync();
const versionInfo = `export const gitVersion = ${JSON.stringify(gitInfo, null, 2)};`
writeFileSync('src/git-version.ts', versionInfo);

Then, run it - it will generate a TS file. Import this from app component (or wherever) and:

import { gitVersion } from "../git-version";

printGitVersion() {
    console.log(`Hash: ${gitVersion.hash}`);
}

Then on CI build:
"build:ci": "npm run clean && node git-version.js && npm run build"

This will make sure the build has the latest commit hash. There are other variations of this but yes, would be nice to do this without hacks.

Went with replacing values in my environments.* with a script, but being able to pass arguments to ng build and retrieve them at runtime would be a very welcome feature. Adding a +1 to the original ticket.

Its pretty easy to inject a config into your application at runtime

https://www.tektutorialshub.com/angular/angular-runtime-configuration/

app.module.ts

..... 
 providers: [
    AppConfigService,
    {
      provide: APP_INITIALIZER,
      useFactory: (appConfig: AppConfigService) => {
        return () => appConfig.load();
      },
      deps: [AppConfigService],
      multi: true,
    },
    {
      provide: LOCALE_ID,
      useFactory: (localeService: LocaleService) => localeService.current,
      deps: [LocaleService],
    },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {
}

app-config.service.ts

export class AppConfigService {

  constructor(private http: HttpClient) {
  }

  static _settings: IAppConfig;

  public get settings() {
    return AppConfigService._settings;
  }

  public load(): Promise<void> {

    const jsonFile = `config/config.${environment.name}.json`;

    return new Promise<void>((resolve, reject) => {
      this.http.get(jsonFile).toPromise().then((response: IAppConfig) => {
        AppConfigService._settings = response as IAppConfig;
        resolve();
      }).catch((response: any) => {
        reject(`Could not load file '${jsonFile}': ${JSON.stringify(response)}`);
      });
    });
  }
}

I just replace config.deploy.json with the appropriate file for the env my build is deployed to

image

Just want to give another solution in the meantime that feels much less hacky to me, using the JSON tool jq, which is available on most package repositories and super lightweight to install.

For a while now, I've used this snippet to grab a value from package.json during build time and inject it into my app at runtime using the environment files.

import { version } from '../../package.json'; // your relative pathing may be different of course

export const environment = {
  production: false,
  version
}

There is a compiler option required to enable this sort of import (resolveJsonModule) but it works great.

Recently, I wanted more dynamic versioning based on information from my CI build pipeline, stuff like the build number and commit hash, and ended up on this GitHub issue. In AngularJS and gulp days, I'd used methods similar to those suggested here, stuff like string replacements prior to actual compilation, but those don't feel quite as a good in the ng build world.

I realized if I can just replace a value in package.json just prior to ng build, my environment.ts can just import it like it already has been with version and go about its business none the wiser.

So using only cat and jq, here's the one-liner to do it:

cat <<< $(jq '.version=env.VERSION' package.json) > package.json
ng build

This overwrites package.json with an identical copy, but with the version key set to the value of VERSION.

You can obviously customize this to be whatever key you'd like, like build or release, or even some nested value if you wanted, just check the jq docs for how to do that.

If you don't like touching your package.json file, you can also use some other variables.json file or whatever you'd like, and have default values that get overwritten so that Intellisense works properly. It's flexible enough to work with any JSON file of course.

Hope this helps someone else! Feels sufficiently un-hacky to me so I'm satisfied.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

gotschmarcel picture gotschmarcel  ·  3Comments

delasteve picture delasteve  ·  3Comments

hartjo picture hartjo  ·  3Comments

brtnshrdr picture brtnshrdr  ·  3Comments

donaldallen picture donaldallen  ·  3Comments