Davut Gürbüz
3 min readMar 1, 2020

--

Is it a good deployment practice to rely on Env files for Angular2+ ?

Photo by Markus Spiske on Unsplash

No doubt all Angular2+ developer know about different environments dev,prod,,,,myEnv environments. Official documentation encourage developers to follow a practice to compile code for different environments.

see official doc at https://angular.io/guide/build

└──myProject/src/environments/
└──environment.ts
└──environment.prod.ts
└──environment.stage.ts

Having env.ts as a typescript file has a clear, dynamic modification advantage since you can have programming functionality in it and it’s not a static file. However, angular cli will compile it and embed everything into your minified and uglified code. This is not something we really demand in DevOps in general. Especially if you have several environments.

If we need to illustrate, for any little change you need to compile again N times. Because your config will be minified and uglified and be part of your deliverable.

ng build — prodSite1
,,,
ng build — prodSiteN

Here is my solution for this hassle. I hope this helps others and I’m looking forward to hearing from all the community to learn better practices.

First of all, I decided to use a simple json file, not to take environment files in my compilation execution. I places an env.json file under assets as seen below.

assets/env.json

{
“baseUrl” : “http://10.10.10.dev/",
“socketUrl”: “ws://10.10.10.dev/”,
“tokenUrl”: “http://10.10.10.dev/"
}

and an Env class representing this JSON configuration.

export class Env {
baseUrl:any;
socketUrl:any;
tokenUrl:any;
}

coded AppInitService to utilize from env.json.

import { Injectable } from ‘@angular/core’;import { HttpClient } from ‘@angular/common/http’;import { Env } from ‘src/Env’;@Injectable()export class AppInitService {public env:Env;constructor(private http:HttpClient) {}Init():Promise<Env> {return new Promise<Env>((resolve, reject) => {////do your initialization stuff herevar resolveResult=null;return this.http.get<Env>(‘assets/env.json’).subscribe((val)=>{this.env=val;if(!this.env || !this.env.baseUrl)this.initDefaults();resolveResult=this.env;//resolve();},err=>{this.initDefaults();console.error(err);resolveResult=err;},()=>{resolve(resolveResult);})});}initDefaults(){this.env=new Env();this.env.baseUrl= window.location.protocol+”//”+window.location.host+”/” ;this.env.socketUrl=’ws://’+window.location.host+’/’;this.env.tokenUrl=this.env.tokenUrl;}}

After placing this service to our app.module.ts as following it’ll be ready to use.

app.module.tsimport { AppInitService } from './applicationSettings/app-init.service';export function initializeApp(appInitService: AppInitService) {return (): Promise<Env> => {return appInitService.Init();}}export function initializeScopeService(appInitService: AppInitService,scopeService: ScopeService) {return ():Promise<any> =>{return appInitService.Init().then(()=>{return scopeService.Init();})}providers: [AppInitService,,,,]}

This is how we use it in our components or services. Please notice we are giving AppInitService as a dependency to XYZ service. So, it will depend on AppInitService. In another word AppInitService will be created and injected first. Use in components is quite similar.

export class XYZService {domain: string = this.app.env.baseUrl;constructor(private _http: HttpClient, private app: AppInitService) {}getXYZInfoList(): Observable<XYZInfo[]> {return this._http.get<SwitchInfo[]>(this.domain + 'api/list/xyzinfo').pipe(delay(1500));}
...

That’s all. You don’t need to compile your code for an environment change anymore. If you have multiple environments you can deploy once and just copy your delivery without overwriting assets/env.json unless it’s necessary to replace.

Starting right will make your life easier. Even though refactoring is an indispensable part of our routine.

\

--

--