Load Configuration Environment Variables into Angular Efficiently When Using SSR

Francesco Cantarella
Apr 5 · 3 min read

Often is useful to load environment variables without rebuilding the project, like when deploying the same project to different servers (or docker instances) with a different configuration.

The target is to load the config WITHOUT wait and perform an HTTP request.
This technique avoid a negative impact on the first load page (did someone say Lighthouse?).

So the best strategy is to load the environment variables when run the server-side rendering (SSR) node process and store they into the index.html file.
See how to do it:

  1. Put the environment variables into a json file, and save as env.json into assets folder:
env.json{
"apiUrl": "https://example.com",
"version": "1.1",

}

2. Modify the index.html placing a placeholder for easily substitute the variables:

index.html<html>
<head>
<!-- ENV BEGIN -->
<!-- ENV END -->

</head>
</html>

3. Load and substitute the variables into index.html file when run the node SSR process:

server.ts...
const injectEnvInline = function (filename, envs) {
let _template_index = fs.readFileSync(filename).toString();
// Find env script link
const env_begin_position = _template_index.indexOf('<!-- ENV BEGIN -->');
const env_end_position = _template_index.indexOf('<!-- ENV END -->');
const env_link = _template_index.substring(env_begin_position, env_end_position);

// Remove env script link and injecting inline environment variables_template_index = _template_index.replace(env_link, envs);
fs.writeFileSync(filename , _template_index, {encoding: 'utf-8'});
};
// Read environment settings
const settings = JSON.parse(fs.readFileSync('assets/env.json').toString());
const env: any = [];
let inline_env = '<!-- ENV BEGIN --><script>window[\'_env\'] = []; ';
for (const key in settings) {
if (settings.hasOwnProperty(key)) {
env[key] = settings[key];
inline_env += 'window[\'_env\'].' + key + '= \'' + settings[key] + '\'; ';
}
}
inline_env += '</script>';
injectEnvInline('index.html', inline_env);

4. Only if we are using angular service worker (ngsw):
Due we modified the index.html file then we must update his hash stored into ngsw.json file.

server.ts...
const changeIndexHtmlHash = function (filename, newHash: string) {
let _template_ngsw = fs.readFileSync(filename).toString();
// Find index.html hash row
const index_begin_position = template_ngsw.lastIndexOf('"/index.html": "');
const index_end_position = index_begin_position + 57;
const new_index_hash = _template_ngsw.substring(index_begin_position, index_end_position);
_template_ngsw = _template_ngsw.replace(new_index_hash, '"/index.html": "' + newHash + '"');
fs.writeFileSync(filename , _template_ngsw, {encoding: 'utf-8'});
};
// Change index.html hash into ngsw.json
const shasum = crypto.createHash('sha1');
const indexHtmlStream = fs.ReadStream('./index.html');
indexHtmlStream.on('data', function(d) { shasum.update(d); });
indexHtmlStream.on('end', function() {
const newHash = shasum.digest('hex');
changeIndexHtmlHash('ngsw.json', newHash);
});

5. Pass the variables and inject they into the relative service when running the server side mode:

server.ts...
app.get('*', function(req, res) {
res.render('index', {
req, res, providers: [
{ provide: 'envSettings', useValue: env }
]
}
});
});

6. Create the env service file:

env.service.tsimport {Inject, Injectable, Optional} from '@angular/core';export interface IEnvService {
"apiUrl": string;
"version": string;
}
@Injectable({providedIn: 'root'})
export class EnvService {
settings: IEnvService = {} as null;
constructor(@Inject('envSettings') @Optional() private envSettings?: any) {
if (envSettings) {
this.settings = envSettings;
}
}
}

7. Inject the env service into app.server.module.ts file:

...
providers: [EnvService]

8. Load the environment variables when running the client side mode:
(Note: we load the env.json file when running in development mode and read the window[‘_env’] keys when running in production mode)

main.ts...
document.addEventListener('DOMContentLoaded', () => {
const start = (settings) => {
// environment.settings = settings.settings;
platformBrowserDynamic([{provide: 'envSettings', useValue: settings}])
.bootstrapModule(AppModule, {preserveWhitespaces: true})
.catch(err => console.log(err));
};
let _env: any = {};
// In production mode
if (window && window['_env'] !== undefined) {
const browserWindow = window;
const browserWindowEnv = browserWindow['_env'] || {};
// Assign environment variables from browser window to env
for (const key in browserWindowEnv) {
if (browserWindowEnv.hasOwnProperty(key)) {
_env[key] = window['_env'][key];
}
}
start(_env);
}
// In development mode
else {
const xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
const response: IEnvService = JSON.parse(xhttp.responseText);
_env = response;
start(_env);
}
};
xhttp.open('GET', 'assets/env.json', true);
xhttp.send();
}
});

9. Use env service whenever you want:

app.module.tsimport {EnvService} from './env.service';
...
constructor(private envService: EnvService) {}
foo() {
console.log('version', this.envService.settings.version);
}

With these simple steps we have reached the target to load environment variables without rebuild the project, without an HTTP request
and use the same file configuration (env.json) during development process.

Geek Culture

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store