Angular6 Runtime environment Variables

2018-05-31CODING ·angular environment-variables

Setting environment variables in your Angular application is pretty straight forward. But, what about when you want to set those variables at runtime depending on the environment you are running in?Out of the box, Angular provides a convenient method for setting environment variables for each our local dev environment and our production environment.
First we set our environment variables per environment:
environments/environment.ts

export const environment = {
production: false,
api: ‘https://localhost:1234/api’
};

environments/environment.prod.ts

export const environment = {
production: true,
api: ‘https://www.example.com/api’
};

And then when we build for Prod we simply need to tell webpack that we are building for prod and it performs a file substitution for us
../angular.json

“configurations”: {
“production”: {
“fileReplacements”: [
{
“replace”: “src/environments/environment.ts”,
“with”: “src/environments/environment.prod.ts”
}
]
},
}

ng build –prod

However, this solution doesn’t account for running in a CD (Continuous Deployment) pipeline. The problem lies in the fact that our build server will build the application only once for production, and then our CD tooling will move the build payload through our various environments: Dev, Stage, Test, Prod.
We could have an environment.*.ts file for each of my environments, then update my webpack config to substitute these files per environment.
BUT… We don’t really have the opportunity to rebuild the product for the intermediate environments. Besides even if we changed our process to handle this, this isn’t good practice anyway, as we should maintain a single payload through the entire process.
Put simply, what we are after is a way to configure the environment variables at runtime, not compile time. These days .NET Core handles this by retrieving the settings from appSettings.json (in the past web.config). Then we can then tell my deployment tooling (Octopus Deploy, VSTS, etc) to update this file each time the application is promoted through the pipeline.
To solve this problem in Angular, my first approach was to create an appSettings.json file, deploy it as an asset with my build, and have the existing environment.ts file(s) pull the values from here.
assets/environment.json

{
“api”: “https://localhost:1234/api”
}

environments/environment.ts

declare function require(url: string);
var json = require(‘./environment.json’);

export const environment = {
production: false,
api: json.api
};

Should this work, nothing else in my application needs to change
BUT This approach didn’t work, and I can only surmise that it is due to the fact that environment is exported as a const, and as such the values are evaluated at compile time (not runtime).
In search of an alternative method, I came across the following post from Juri Strumpflohner. A simple solution that solves my problems, but also allows me to take it a step further and fetch some/all of the variables from an external service.
The Solution
Create the environment.json file
assets/environment.json

{
“api”: “https://localhost:1234/api”
}

I place this file in the assets folder, to ensure it gets distributed with the application.
[optional] Define a model of the config file
models/environment.ts

export class environment {
api: string;
}

now config service that fetches the config
service/environment.services.ts

import { Injectable } from ‘@angular/core’;
import { HttpClient } from ‘@angular/common/http’;
import { appSettings } from ‘../models/appSettings’;

@Injectable()
export class EnvironmentService {
private appConfig : appSettings;

constructor(private http: HttpClient) { }

loadAppConfig() {
return this.http.get(‘/assets/environment.json’)
.toPromise()
.then(data = > {
this.appConfig = data as appSettings;
});
}

getConfig() : appSettings {
return this.appConfig;
}
}

Note: We are using the HttpClient to fetch the settings from a file. We could also call an endpoint to fetch the environment variables too
Note: As explained by Juri, The loadAppConfig() method needs to return a promise, as opposed to an Observable, as the API consuming this service currently only works with Promises.
Now for the magic: Configure the service to run as an app initialiser

import { NgModule, APP_INITIALIZER } from ‘@angular/core’;
import { EnvironmentService } from ‘services/environment.service.ts’;

@NgModule({
declarations: [
AppComponent
],
imports: [
HttpClientModule
],
providers: [
EnvironmentService,
{
provide: APP_INITIALIZER,
useFactory: (svc: EnvironmentService) = > { return () = > svc.loadAppConfig(); },
multi: true,
deps: [EnvironmentService]
}
],
bootstrap: [AppComponent]
})
export class AppModule { }

Note: APP_INITIALIZER is an injection token that will instruct the application to run the factory method on application startup. In our case, this will result in calling our loadAppConfig() method
Note: ‘multi: true’ is required when providing APP_INITIALIZER. ‘multi: true’ tells the Dependency Injection container, that there can be more than one instance of the provided service (and to provide them all)
Update all references from environment to pull the settings from config.service
Finally, update my release definitions to perform the variable substitution for each environment.

Read More

Ng directive Thought Experiment

ngLet directive to resolve an async observable binding without having to use ngIf to hide contentNot long after publishing my recent post on minimising Async bindings in Angular by using the *ngIf=”obs$ | async as obs”; construct, I received the following comment on twitter:

it’s a pity they enabled the “let” option on ngif. what if i dont want to hide the section before the data arrive? May East (@mayeast) May 2, 2018

This got me thinking… Is there an existing way to do this? Is it possible? and even is it useful? After looking around into the ngIf documentation on Angular.io Structural Drirectives and then into the ngIf directive source/packages/common/src/directives/ng_if.ts, I figured it couldn’t be too hard to create a “Let” directive… As it turns out a Let directive is exactly the same as the ngIf directive without the “else“…
Acknowledgement: None of this is my code, I’ve just borrowed it from the existing ngIf directive
So let’s try
Step 1: Generate the directive

ng g directive ngLet

Step 2: Borrowing from ngIf, create an NgLetContext

export class ngLetContext {
public $implicit: any = null;
public ngLet: any = null;
}

Step 3: Declare and initialise local variables
a. The current context b. Reference to the template c. Reference to the currently embedded view

private _thenTemplateRef: TemplateRef | null = null;
private _thenViewRef: EmbeddedViewRef | null = null;
private _context: ngLetContext = new DhsLetContext();

Step 4: Inject into the constructor a “ViewContainerRef” and a “TemplateRef” and initialise our templateRef
a. The ViewContainerRef, is a reference to where our rendered view will be hosted
b. the TemplateRef, is a reference to the template within of our content

constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef) {
this._thenTemplateRef = templateRef;
}

Step 5: Define an Input property to accept the binding and update the view ref

@Input() set ngLet(condition: any) {
this._context.$implicit = this._context.ngLet = condition;
this._updateView();
}

Step 6: Update the current view
If there is a condition to evaluate, and we don’t already have a view, then ask our host container to create one based on our template and set the context.

private _updateView() {
if (this._context.$implicit) {
if (!this._thenViewRef) {
this._viewContainer.clear();
if (this._thenTemplateRef) {
this._thenViewRef = this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
}
}
}
}

Step 7: Ensure the directive is declared in your module (this should be done for you if you used the cli to generate the directive for you)

@NgModule({
declarations: [
AppComponent,
NgLet
],

Final step: Put it to use

{{person?.name}}

Final directive class

import { Directive, ViewContainerRef, TemplateRef, Input, EmbeddedViewRef } from ‘@angular/core’;

@Directive({
selector: ‘[ngLet]’
})
export class LetDirective {

private _thenTemplateRef: TemplateRef | null = null;
private _thenViewRef: EmbeddedViewRef | null = null;
private _context: NgLetContext = new NgLetContext();

constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef) {
this._thenTemplateRef = templateRef;
}

@Input()
set ngLet(condition: any) {
this._context.$implicit = this._context.ngLet = condition;
this._updateView();
}
private _updateView() {
if (this._context.$implicit) {
if (!this._thenViewRef) {
this._viewContainer.clear();
if (this._thenTemplateRef) {
this._thenViewRef =
this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
}
}
}
}
}

export class NgLetContext {
public $implicit: any = null;
public ngLet: any = null;
}

Although it is possible to create a “Let” directive, I’m not quite convinced on it’s usefulness at this stage, as each inner binding will require the ‘?.’ null check to avoid a ‘name not found on undefined” binding error and the issues associated with such binding errors. Either way, thanks for the challenge @mayeast

Read More

Angular: arrrgh… too many async bindings

2018-05-01CODING ·angular rxjs async

Redundant async bindings will trigger excess invocations of your observable. This simple technique, lets you remove those excess subscriptionsIn my previous post I came across a quirk with Angular, Observables and the Async pipe. That is, each subscription to an observable will initiate the observables execution. Hence we were calling the api once per async subscription.

A subscribe call is simply a way to start an “Observable execution” and deliver values or events to an Observer of that execution.https://reactivex.io

Original version

{{ (result$ | async).name }}
{{ (result$ | async).surname }}

result$ = this.http.get(‘/api/person/1’);

My original attempt to resolve this, was to first subscribe to the observable in the typescript file, and then bind directly to the results property.
Attempt 1

{{ result.name }}
{{ result.surname }}

result: Person;

this.http.get(‘/api/person/1’).
subscribe(person = > this.result = person);

However, this now raises some more problems for us.
We get the following error: ERROR TypeError: Cannot read property ‘name’ of undefined. That is, before the http request returns and we set the result property, our bindings are throwing an error.
For every subscription to an observable, we need to manage the unsubscription. Normally Async pipes handle this for us.
To solve the first problem we have two options. We need to either: a) perform a null check on each binding
Attempt 2.1

{{ result?.name }}
{{ result?.surname }}

or b) wrap the bindings in a div and hide it with an *ngIf
Attempt 2.2

{{ result.name }};
{{ result.surname }}

Neither of these options help us with out second problem, the need to unsubscribe for each subscription. Enter this post from Todd Motto that I came across today, with a neat solution to both of the above problems.
Attempt 3

{{ result.name }}
{{ result.surname }}

Todd then offers a neat little progression to the above, to show an alternative template, when the above div is hidden
Final solution

{{ result.name }}
{{ result.surname }}

Loading Person…

Thanks, Todd, this simple hint will help tidy up a lot of my code.

Read More