Angular custom environments without changing the local code or repo

In this article we gonna talk about a scenario when we want to set up our custom environment by neither changing local code nor pushing to repository.
Problem
As you know Angular provides us environment files where we can store our config. But we still fill those files by writing statically our secret keys or settings. What if due to security, we do not want to share our secret keys to repository? Or what if we want our custom settings in environment and still don’t want to push those changes? The first ideal solution comes to mind is to set up environment file which retrieves this config from System Environment variables(.env) like below.
function getApiBasePath(): string {
return (window as any).config.API_BASE_PATH || ${process.env.DEFAULT_API_URL}';
}
export const environment = {
production: false,
API_BASE_PATH: getApiBasePath(),
MSAL: {
CLIENT_ID: ${process.env.MSAL_CLIENT_ID}',
AUTHORITY: ${process.env.MSAL_AUTHORITY}'',
},
debugStream: false,
};
Unfortunately, the above solution simply will not work. Because we are accessing system environment variables via process.env
but process.env
is available to Node
applications, while an Angular application is not.
Solution ✍
We can solve problem of reading system environment variables solution by using dotenv
1. Install dotenv
npm i dotenv --save-dev
2. Create new file(set-env.ts) in root of your Angular applicaiton.
This will let us process system environment variables (.env), create new or replace file called environment.custom.ts (targetPath propety).
const { writeFile } = require('fs');
// Your environment.custom.ts file. Will be ignored by git.
const targetPath = './src/environments/environment.custom.ts';
// Load dotenv to work with process.env
require('dotenv').config();
// environment.ts file structure
const envConfigFile = `
function getApiBasePath(): string {
return (window as any).config.API_BASE_PATH || 'default-url';
}
export const environment = {
production: false,
API_BASE_PATH: getApiBasePath(),
MSAL: {
CLIENT_ID: '${process.env.MSAL_CLIENT_ID}',
AUTHORITY: '${process.env.MSAL_AUTHORITY}',
},
debugStream: '${process.env.DEBUG_STREAM}',
};
`;
writeFile(targetPath, envConfigFile, function (err) {
if (err) {
throw console.error(err);
} else {
console.log('Using custom environment');
}
});
3. Add environment.custom.ts file to .gitignore
in order to exlude our generated environment.custom.ts from being committed to repo.
# custom files
/src/environments/environment.custom.ts
4. By default Angular provides us two environments (environment.ts and environment.prod.ts). Now we should let Angular know we have new custom environment file and the way to handle it (for serving ,building, testing). Add this bolded custom entry to angular.json for building (inside architect/build/configurations object)
"your-projectName": {
...
"architect": {
"build": {
...
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
},
"custom": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.custom.ts"
}
]
},
}
...
}
...
}
...
}
5. Add custom entry for ng serve (inside architect/serve/configurations object)
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "your-project-name:build",
"proxyConfig": "proxy.conf.json"
},
"configurations": {
"production": {
"browserTarget": "your-project-name:build:production"
},
"custom": {
"browserTarget": "your-project-name:build:custom"
}
}
},
6. Add custom entry to e2e (inside architect/e2e/configurations object)
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js"
"devServerTarget": "your-project-name:serve"
},
"configurations": {
"production": {
"devServerTarget": "your-project-name:serve:production"
},
"custom": {
"devServerTarget": "your-project-name:serve:custom"
}
}
}
7. Add or modify our code snippet to package.json to process our set-env.ts file.
{
...
"scripts": {
"ng": "ng",
"config": "ts-node set-env.ts",
"start": "npm run config && ng serve --configuration=custom",
"build": "npm run config && ng build --configuration=custom",
...
},
...
}
Here we goo , thats all for the problem. Lets summarize what happens here when we run npm run start
.
ts-node
will run set-env.ts
which will process our system environment variables and create new file which content will be simply the envConfigFile
constant in set-env.ts file. Finally it will run ng serve which will use our new environment.custom.ts file as configuration.Also important, our commit is clean because we exluded our environment.custom.ts from changes.
That’s all, hope you enjoyed the reading and benefit from it.☕