VSCode Multiline Editing Magic

I used Visual Studio Code’s multiline editing today and was pleasantly surprised.

This feature is available in a few editors, pick your favorite one and be more productive :)

Use it…don’t use it :)

Add Angular Tests to Netlify Deployment

For quite some time, Netlify has been my (free) tool of choice to deploy my blog and other personal projects.
It’s very simple to set-up and their documentation is also easy to follow.

But how do you make your Angular tests part of the Netlify build pipeline?

Most of the examples suggest: Build command: ng build --prod
What I need: Build command: ng test && ng build --prod

Netlify build settings sample

My first issue, ng test creates an instance of Chrome browser as specified in the karma.conf.js.
Pretty simple to solve, run Headless Chrome.

But how do I get Headless Chrome installed on Netlify’s Linux build environment? Puppeteer.

I solved it with a quick npm install puppeteer.

I’ve updated my package.json:

1
2
3
4
5
6
7
8
9
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --prod",
"test": "ng test",
"test-headless": "ng test --watch=false --browsers=ChromeHeadless",
"lint": "ng lint",
"e2e": "ng e2e"
},

I’ve tested everything locally and Build command: npm run-script test-headless && npm run-script build executed successfully in my Windows environment.

After I’ve pushed the new changes, the build failed - “CHROME_BIN” env variable error.

1
2
3
4
5
6
7
8
9
10
11
12
{"os":"linux","arch":"x64"})
11:33:41 AM: audited 34037 packages in 15.642s
11:33:41 AM: found 0 vulnerabilities
11:33:41 AM: > beukes-bunch-health-tracker@0.0.0 test /opt/build/repo/ui
11:33:41 AM: > ng test
11:34:02 AM: 10 03 2019 03:34:02.050:INFO [karma-server]: Karma v4.0.1 server started at http://0.0.0.0:9876/
11:34:02 AM: 10 03 2019 03:34:02.053:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
11:34:02 AM: 10 03 2019 03:34:02.060:INFO [launcher]: Starting browser Chrome
11:34:02 AM: 10 03 2019 03:34:02.061:ERROR [launcher]: No binary for Chrome browser on your platform.
11:34:02 AM: Please, set "CHROME_BIN" env variable.
11:34:02 AM: npm
11:34:02 AM: ERR! Test failed. See above for more details.

Seems like karma is looking for the path of the chrome executable.

Second issue, how do I set CHROME_BIN env variable path?

Thanks to the folks at Puppeteer they made it easy by just checking the build logs:

1
2
3
4
5
1:00:59 PM: > puppeteer@1.13.0 install /opt/build/repo/ui/node_modules/puppeteer
1:00:59 PM: > node install.js
1:01:07 PM: Chromium downloaded to /opt/build/repo/ui/node_modules/puppeteer/.local-chromium/linux-637110
1:01:07 PM: > node-sass@4.11.0 postinstall /opt/build/repo/ui/node_modules/node-sass
1:01:07 PM: > node scripts/build.js

I’m not fluent in Linux, but I found the printenv command.
It prints all or part of the environment variables.

New plan: Build command: printenv && npm run-script test-headless && npm run-script build

With a bit of trail and error I discovered:
CHROME_BIN=/opt/build/repo/ui/node_modules/puppeteer/.local-chromium/linux-637110/chrome-linux/chrome

From the printenv I’ve learned that env variable PWD=/opt/build/repo/ui.

I could use the PWD variable like this:
CHROME_BIN=${PWD}/node_modules/puppeteer/.local-chromium/linux-637110/chrome-linux/chrome

But, linux-637110 smells like an appended build number.

I used this hack to dynamically look up the linux-xxxxxx folder name:
ls -x -1 ${PWD}/node_modules/puppeteer/.local-chromium.

My ugly CHROME_BIN env variable (but it works):
CHROME_BIN=${PWD}/node_modules/puppeteer/.local-chromium/$(ls -x -1 ${PWD}/node_modules/puppeteer/.local-chromium)/chrome-linux/chrome

One can set a Linux env variable with export.

My final plan:
Build command: export CHROME_BIN=${PWD}/node_modules/puppeteer/.local-chromium/$(ls -x -1 ${PWD}/node_modules/puppeteer/.local-chromium)/chrome-linux/chrome && printenv && npm run-script test-headless && npm run-script build

To save me the time logging into Netlify and update the build command, I’ve added the netlify.toml file.

1
2
3
4
[build]
base = "ui"
command = "export CHROME_BIN=${PWD}/node_modules/puppeteer/.local-chromium/$(ls -x -1 ${PWD}/node_modules/puppeteer/.local-chromium)/chrome-linux/chrome && printenv && npm run-script test-headless && npm run-script build"
publish = "ui/dist/beukes-bunch-health-tracker"

GREEN - build log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
...
...
7:13:24 PM: LANGUAGE=en_US:en
7:13:24 PM: YARN_VERSION=1.3.2
7:13:24 PM: rvm_ruby_string=ruby-2.3.6
7:13:24 PM: GIMME_GO_VERSION=1.10
7:13:24 PM: CHROME_BIN=/opt/build/repo/ui/node_modules/puppeteer/.local-chromium/linux-637110/chrome-linux/chrome
7:13:24 PM: GOCACHE=/opt/buildhome/.gimme_cache/gocache
7:13:24 PM: GEM_PATH=/opt/buildhome/.rvm/gems/ruby-2.3.6:/opt/buildhome/.rvm/gems/ruby-2.3.6@global
7:13:24 PM: > ng test --watch=false --browsers=ChromeHeadless
7:13:36 PM: 10 03 2019 11:13:36.774:INFO [karma-server]: Karma v4.0.1 server started at http://0.0.0.0:9876/
7:13:36 PM: 10 03 2019 11:13:36.776:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
7:13:36 PM: 10 03 2019 11:13:36.780:INFO [launcher]: Starting browser ChromeHeadless
7:13:46 PM: 10 03 2019 11:13:46.808:INFO [HeadlessChrome 74.0.3723 (Linux 0.0.0)]: Connected on socket POrD2-FtM3GJq9JLAAAA with id 8678984
7:13:51 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 0 of 14 SUCCESS (0 secs / 0 secs)
7:13:52 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 1 of 14 SUCCESS (0 secs / 0.85 secs)
7:13:52 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 2 of 14 SUCCESS (0 secs / 1.562 secs)
7:13:53 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 3 of 14 SUCCESS (0 secs / 1.821 secs)
7:13:53 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 4 of 14 SUCCESS (0 secs / 1.85 secs)
7:13:53 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 5 of 14 SUCCESS (0 secs / 2.103 secs)
7:13:53 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 6 of 14 SUCCESS (0 secs / 2.332 secs)
7:13:53 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 7 of 14 SUCCESS (0 secs / 2.363 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 8 of 14 SUCCESS (0 secs / 2.92 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 9 of 14 SUCCESS (0 secs / 2.938 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 10 of 14 SUCCESS (0 secs / 3.219 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 11 of 14 SUCCESS (0 secs / 3.485 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 12 of 14 SUCCESS (0 secs / 3.494 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 13 of 14 SUCCESS (0 secs / 3.501 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 14 of 14 SUCCESS (0 secs / 3.657 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 14 of 14 SUCCESS (3.682 secs / 3.657 secs)
7:13:54 PM: TOTAL: 14 SUCCESS
7:13:54 PM: TOTAL: 14 SUCCESS
7:13:55 PM: > ng build --prod
7:15:24 PM: Date: 2019-03-10T11:15:24.219Z
7:15:24 PM: Hash: 64ca61b254dcfd5acdc1
7:15:24 PM: Time: 85158ms
7:15:24 PM: chunk {0} runtime.a5dd35324ddfd942bef1.js (runtime) 1.41 kB [entry] [rendered]
7:15:24 PM: chunk {1} es2015-polyfills.4a4cfea0ce682043f4e9.js (es2015-polyfills) 56.4 kB [initial] [rendered]
7:15:24 PM: chunk {2} main.3abc8440f2ae7fd24e18.js (main) 1.59 MB [initial] [rendered]
7:15:24 PM: chunk {3} polyfills.7e5c029b78344f3b7d7c.js (polyfills) 41.1 kB [initial] [rendered]
7:15:24 PM: chunk {4} styles.e230360cfdba5044352a.css (styles) 61.9 kB [initial] [rendered]
...
...

RED - build log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
...
7:06:09 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 8 of 14 SUCCESS (0 secs / 2.921 secs)
7:06:09 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 9 of 14 SUCCESS (0 secs / 2.938 secs)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 10 of 14 SUCCESS (0 secs / 3.244 secs)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0) AuthenticationService should be read tokens FAILED
7:06:10 PM: Expected 'Bearer' to be 'BearerRuan'.
7:06:10 PM: at UserContext.<anonymous> (src/app/services/authentication.service.spec.ts:65:31)
7:06:10 PM: at TestBedViewEngine.push../node_modules/@angular/core/fesm5/testing.js.TestBedViewEngine.execute (node_modules/@angular/core/fesm5/testing.js:1822:1)
7:06:10 PM: at UserContext.<anonymous> (node_modules/@angular/core/fesm5/testing.js:1991:29)
7:06:10 PM: at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:391:1)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 11 of 14 (1 FAILED) (0 secs / 3.671 secs)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0) AuthenticationService should be read tokens FAILED
7:06:10 PM: Expected 'Bearer' to be 'BearerRuan'.
7:06:10 PM: at UserContext.<anonymous> (src/app/services/authentication.service.spec.ts:65:31)
7:06:10 PM: at TestBedViewEngine.push../node_modules/@angular/core/fesm5/testing.js.TestBedViewEngine.execute (node_modules/@angular/core/fesm5/testing.js:1822:1)
7:06:10 PM: at UserContext.<anonymous> (node_modules/@angular/core/fesm5/testing.js:1991:29)
7:06:10 PM: at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:391:1)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 12 of 14 (1 FAILED) (0 secs / 3.689 secs)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 13 of 14 (1 FAILED) (0 secs / 3.695 secs)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 14 of 14 (1 FAILED) (0 secs / 3.856 secs)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 14 of 14 (1 FAILED) (3.883 secs / 3.856 secs)
7:06:10 PM: TOTAL: 1 FAILED, 13 SUCCESS
7:06:10 PM: TOTAL: 1 FAILED, 13 SUCCESS
7:06:10 PM: npm
7:06:10 PM: ERR! code ELIFECYCLE
7:06:10 PM: npm
7:06:10 PM: ERR! errno 1
7:06:10 PM: npm ERR!
7:06:10 PM: beukes-bunch-health-tracker@0.0.0 test-headless: `ng test --watch=false --browsers=ChromeHeadless`
7:06:10 PM: npm ERR!
7:06:10 PM: Exit status 1
7:06:10 PM: npm
7:06:10 PM: ERR!
7:06:10 PM: npm
7:06:10 PM: failed during stage 'building site': Build script returned non-zero exit code: 1
7:06:10 PM: ERR! Failed at the beukes-bunch-health-tracker@0.0.0 test-headless script.
7:06:10 PM: npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
7:06:10 PM: npm
7:06:10 PM: ERR! A complete log of this run can be found in:
7:06:10 PM: npm
7:06:10 PM: Shutting down logging, 17 messages pending

Please leave a comment on how to enhance my ball of mud :)

Use it…don’t use it :)

Kudos to:
Todd Palmer - Angular Testing with Headless Chrome
Alain Chautard - How-to Running Angular tests on continuous integration servers

When Your Tests Are at the Mercy of Your Database

(Tech: C#, Microsoft SQL)

Recently I was investigating a bug which kept me, and a few of my mates, occupied way longer than we are willing to admit :)

One summer’s morning

It started on a fresh summer’s morning…

After I’ve completed my code changes, like a very good software citizen, I ran the project’s unit tests.
They were all green and I was riding the wave of success, nothing could stand in my way today…I’m flying through my work.
Last formality is to create a Pull Request and wait for the build server to succeed before reviewers will approve my changes.

Life…is…easy, everything is in harmony.

Until I received an email from the build server - Failing tests.
Wait…what?!?!?
They were all green locally, surely this can’t be right.
I kicked off another build, now they pass.
Kicked another build, now they fail.
Kicked off yet another build, failed again.
This carried on a few more times with the hope I’ll find a pattern, no luck.

It was very strange as my changes did not relate to the failing tests.

I then realized it was one of those “fun” intermittent failing tests.

And so my investigation was born…

Investigation

I set my acceptance criteria to 5 successful consecutive builds.

I had two failing tests.
In short, the tests set a property and then saved that property to the database.

1
2
3
4
5
6
7
public void ThenInvoiceCommentIsStored()
{
var invoices = GetInvoices(); //retrieving actual database data

//assert
invoices.Lines[0].Comment.ShouldBe("Hello World"); //sometimes fails here
}

The verification failed when that property was expected to have a string value but the actual value was null.

Example:

1
2
3
4
invoices.Lines[0].Comment should be 
"Hello World"
but was
null

Usually when I have intermittent failing tests, past experience taught me dates/times/timezone are involved.
In my case, none of those were present.

I’ve checked server build plans, added debugging messages, second guessing frameworks we used (long shot, I know), adding delays to check race conditions and a few other ridiculous ideas not worth mentioning.

None of my plans worked.

The investigation was a very slow and painful process as every code change took 5 minutes to build.

Die bug, die!

I decided to turn my focus back to the origin of the problem, the failing tests.
I zoomed in on the one particular test - ThenInvoiceCommentIsStored.

By looking at the test, the intent is to assert the first element in the list.
I’ve added a breakpoint and discovered there were actually two items in the Lines list.

Hypothesis: Is it possible we updated the data correctly but the list is only in the wrong order?

I changed the test to find the Line item by ID (filter), rather than the first element in the Lines list.

1
2
3
4
5
6
7
8
9
public void ThenInvoiceCommentIsStored()
{
var invoices = GetInvoices(); //retrieving actual database data

//assert
//_expectedLineID was set before the actual assertion eg: 88
var specificLine = invoices.Lines.Single(x => x.Id == _expectedLineID);
specificLine.Comment.ShouldBe("Hello World");
}

BOOM, the beast was slain…5 successful consecutive builds!

Solution Explained

The actual problem was a mistake in the test.

The test indicated we expect the first element in the list to change.
That holds true, so long as there is only one item in the list.
As soon as we have two items in the list, then we have to investigate sort order.

The extra data silently changed the test.

Because we query the data directly from the database, with no sort order, the database will return the records based on its default sort order.

Our tests were at the mercy of the database sort order.

Usually the database’s default sort order is based on a table’s Primary Key.

In our case, Lines.ID was the Primary Key and was of Data Type uniqueidentifier (GUID).

1
2
3
4
5
6
7
8
CREATE TABLE [dbo].[Lines](
[ID] [uniqueidentifier] NOT NULL,
[Comments] [nvarchar](max) NULL,
CONSTRAINT [PK_Lines] PRIMARY KEY CLUSTERED
(
[ID] ASC
)
)

Example: Two Line records were added in exactly this order but with only one digit difference:

488d6b1a-898e-4a13-acb3-a9e500d66962 (sample only, GUIDS are unique)
288d6b1a-898e-4a13-acb3-a9e500d66962

On a query of the data, even though the data input was done with 4xxxxxxx-xxxx-xxxx... first, 2xxxxxxx-xxxx-xxxx... will always show up first because of the Ascending Primary Key sort order [ID] ASC.

Query:

1
SELECT ID FROM Lines

Result:

1
2
3
ID
288d6b1a-898e-4a13-acb3-a9e500d66962
488d6b1a-898e-4a13-acb3-a9e500d66962

This had a devastating impact on the server unit tests as we had intermittent test failures.
In some cases we were lucky and the GUID order worked out well for asserting the first element in the Lines list, other times the updated Line item was somewhere in the list.

What about the green local tests?

I’m still unclear as to why they passed.

The only difference being that on the server, the database is created every time.
Locally, the database is created only the first time you run the unit tests.

I decided to let this one go as the change fixed the server build and locally the tests still passed.

My initial mission was completed.

What I have learned?

  1. Write your tests with more intent.
    Your test must speak to future you about what you were trying to test.
    When you look back at code you’ve written 3 months ago, you will thank the past you :)

  2. Perseverance - failure was not an option.
    The longer the problem dragged out, the more drive I had to fix the bug.
    Once you’ve invested so much time in an issue, the victory is so much sweeter.

Use it…don’t use it :)

Angular Typesafe Reactive Forms With TypeScript

Problem

Working with more complex Reactive Forms, it was challenging to remember how the form was bound to my FormGroup object.

I wanted intellisense to help me.

This is because the FormGroup class has a ‘value’ property which returns ‘any’, the cause of my frustrations.

Good luck renaming property names!

1
2
3
4
5
//my dream and challenge
get value: CustomType; // <-- somehow the angular code must return a CustomType

//from Angular FormGroup class
get value: any;

Another frustration I faced was referring to the form properties names as ‘string’ values. It quickly got out of hand when I renamed property names.

1
2
3
4
5
6
7
8
//example of usage
this.form.get('heroName').patchValue('He-Man');

//my dream and challenge
this.form.get(x => x.heroName).patchValue('He-Man');

//from Angular FormGroup class
get(path: Array<string|number>|string): AbstractControl|null

Angular team is considering implementing Typesafe Reactive Forms

Here are the issues worth tracking:
* Proposal - ReactiveForms: add AbstractControl.getChild<T> method
* Reactive forms are not strongly typed

Can’t wait for that to be fully implemented…until then, I’ll hack my way to typesafety :)

Solution

Quick Demo

The Interface used for all the code samples

1
2
3
4
5
6
interface IHeroFormModel {
name: string,
secretLairs: Array<Address>,
power: string,
sidekick: string
}

Intellisense on FormGroupTypeSafe<T>.value:

FormGroupTypeSafe.value Intellisense

Intellisense on FormGroupTypeSafe<T>.getSafe and then patching the value:

FormGroupTypeSafe.getSafe Intellisense

By having a lambda like expression instead of a ‘string’ property name, makes refactoring so much easier.

Typesafe Angular Reactive Forms Sample

I modified the Angular Reactive Forms Sample with my Typesafe Angular Reactive Forms Sample.

Search for TypeSafe Reactive Forms Changes to find all the changes. I commented out the original angular code and replaced it with the Typesafe way as a quick reference.

Files to focus on:

  • app/app.modules.ts
  • app/hero-detial.component.ts
  • app/angular-reactive-forms-helper.ts

* app/app.modules.ts *

Two things of importance here:

  • import the FormBuilderTypeSafe class
  • add to provider list for the Dependency Injection

See Typesafe Angular Reactive Forms Sample for details.

* app/hero-detial.component.ts *

  1. Define an interface of your form model.

    1
    2
    3
    4
    5
    6
    7
    //interface used with FormGroupTypeSafe<T>
    interface IHeroFormModel {
    name: string,
    secretLairs: Array<Address>,
    power: string,
    sidekick: string
    }
  2. Declare your new FormGroupTypeSafe form with the help of TypeScript’s generics.

    1
    2
    3
    4
    /* TypeSafe Reactive Forms Changes */
    //old code
    //heroForm: FormGroup;
    heroForm: FormGroupTypeSafe<IHeroFormModel>;
  3. Inject FormBuilderTypeSafe.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    constructor(
    /* TypeSafe Reactive Forms Changes */
    //old code - private fb: FormBuilder,
    private fb: FormBuilderTypeSafe,
    private heroService: HeroService) {

    this.createForm();
    this.logNameChange();
    }
  4. Create your form group with Interfaces (contracts).

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // old code
    // this.heroForm = this.fb.group({
    // name: '',
    // secretLairs: this.fb.array([]),
    // power: '',
    // sidekick: ''
    // });


    this.heroForm = this.fb.group<IHeroFormModel>({
    name: new FormControl(''),
    secretLairs: new FormControl([]),
    power: new FormControl(''),
    sidekick: new FormControl('')
    });

    //***** Nested type sample *****
    interface IAddressModel {
    suburb: string;
    postcode: string;
    }

    interface ICustomerModel {
    name: string;
    address: IAddressModel
    }

    this.form = this.fb.group<ICustomerModel>({
    name: new FormControl(null, [Validators.required]),
    address: this.formBuilder.group<IAddressModel>({
    suburb: new FormControl(''),
    postcode: new FormControl('', [Validators.required]),
    })
    });

Benefits TypeScript provide:

  • Detecting errors (contract mismatch): Let’s say we change the ‘name’ property, ‘name’ => ‘nameNew’, but the IHeroFormModel.name did not change, TypeScript will raise the alarm.

  • Renaming properties: Super easy, I’m using VSCode and it’s as easy as F2, rename the property, enter…and TypeScript will do the rest. No more search and replace.

  • No more misspelled properties: Intellisense ensures you get the correct property name everytime. With string property names a mistake can easily creep in.

  • Guidance on how to create the form group: Code sample below will have TypeScript raise the alarm when attempting to set ‘name’ to ‘’, FormGroupControlsOf<T> returns FormControl | FormGroup.

    message: ‘Argument of type ‘{ name: string; secretLairs: FormControl; power: FormControl; sidekick: FormControl; }’ is not assignable to parameter of type ‘FormGroupControlsOf‘.
    Types of property ‘name’ are incompatible.
    Type ‘string’ is not assignable to type ‘FormControl | FormGroup’.’
    at: ‘1, 49’
    source: ‘ts’

1
2
3
4
5
6
this.heroForm = this.fb.group<IHeroFormModel>({
name: '',
secretLairs: new FormControl([]),
power: new FormControl(''),
sidekick: new FormControl('')
});

5. Use the typesafe form.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//old code 
//this.heroForm.get('name').patchValue('new hero');
this.heroForm.getSafe(x => x.name).patchValue('new Hero');

//old code
//this.heroForm.setControl('secretLairs', addressFormArray);
this.heroForm.setControlSafe(x => x.secretLairs, addressFormArray);

formatAddressToOneLine(addressIndex: number) : string {
//When using angular's FormGroup no intellisense is provided
//when typing 'this.heroForm.value.'.
//The FormGroupTypeSafe form will give you intellisense.
//The benefit is when you rename property names,
//TypeScript will make sure you change all the references

//old code => this.heroForm.value.secretLairs[addressIndex] returns 'any',
//then use 'as Address' to get it to be an Address object for intellisense to work
//let address = this.heroForm.value.secretLairs[addressIndex] as Address;
//No more 'as', TypeScript knows it is an Address
let address = this.heroForm.value.secretLairs[addressIndex];
return address.street + ", " + address.city + ", " + address.state;
}

* app/angular-reactive-forms-helper.ts *

In order to make this work as closely as possible to the angular way I created an abstract class FormGroupTypeSafe<T> which derives from angular’s FormGroup.
It also ensures that exisitng code will not break.
I also created two functions: getSafe, setControlSafe.
Those were the only functions I needed for the Typesafe Angular Reactive Forms Sample.
If you need more, add them :)

Find implementation of angular-reactive-forms-helper.ts here Typesafe Angular Reactive Forms Sample.

Conclusion

I don’t misspell property names anymore and refactoring Reactive Forms is back to being a trivial task.
I think the extra bit of ceremony is absolutely worth it.

Use it…don’t use it :)

Build a Static Secure Blog With Hexo and AWS S3

In this post I will focus on the steps I took to make my first post publicly available as opposed to spending time on how to customise your blog.

I would highly recommend Jeff Ammons Pluralsight course on Build a Better Blog with a Static Site Generator for Hexo customisation.

Topics

Register a Domain

First thing I did was to register a personal domain name - ruanbeukes.net.

Easiest is to google ‘domain registrars’ for heaps of choices.
Choose the very first one in the list and start exploring with domain names.
Once you have identified your available domain name, you are ready to explore other registrars to compare prices.

When the price suits, and you have read a few online reviews about your new registrar, sign up to purchase your domain name.

I pay AUD$11.50 annually.

* Add Privacy Protection and protect yourself from spam and scams when purchasing your domain. *

Think of your domain name as an entry in the yellow pages where someone can lookup your phone number.
I do not want my personal details publicly available.
You can make it private by adding Privacy Protection.

I pay AUD$7 annually.

If you have concerns about your personal details, use whois and enter your domain name.

Register a AWS account to host your blog

Go to Amazon Web Services (AWS) and create a free account.
You will need a credit card to register.
Fill out your credentials and sign up for a Personal Account.

Amazon bills you when you exceed their free usage deal.
It works on a ‘Pay-As-You-Use’ model and is really cheap.
They have plenty of services to choose from but I use their S3 feature for my blog.

Create AWS S3 (Simple Storage Service) bucket

Amazon S3 gives you secure, durable and highly-scalable cloud storage.
Login to your AWS account and find the S3 service under the Services menu.

Hit the Create Bucket button and it will prompt you with a Bucket Name and Region.

* Bucket Name must be your domain name. *
The Region should be the one closest to your location.

Create your bucket!

** Permissions: **
Go to the Properties of the bucket and expand Permissions, click on the Edit bucket policy.

Add this code I found in the AWS docs

1
2
3
4
5
6
7
8
9
10
11
12
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Allow Public Access to All Objects",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::{YourBucketNameHere}/*"
}
]
}

** Static Website Hosting: **
Go to the Properties of the bucket and expand Static Website Hosting, enable website hosting.

Add Index Document - index.html.
Optional to add Error Document - error.html.
The Endpoint is the link you should use if you don’t have a domain name registered.
Note - The bucket endpoint is also used by CloudFlare later in this post.

** Create ‘www’ sub domain bucket and redirect to domain **
I also created a www.ruanbeukes.net bucket which points to ruanbeukes.net.

Create the www.{YourDomainName} bucket and go to its Properties and expand the Static Website Hosting. Select the ‘Redirect all requests to another host name’ option.
Fill out the ‘Redirect all requests to’ text box with the domain bucket name.

The AWS help documentation is very good. There is always a ‘Learn more’ link on the option I explored which explained in detail what the setting or section is all about.

Resource - Hosting a Static Website on Amazon S3

Install Hexo

Hexo is a static blog engine. It is a fast, simple and powerful.

Setup is easy with the Hexo Docs. Prerequisites are Node.js and Git.
Then it’s as easy as

1
$ npm install -g hexo-cli

You can make sure Hexo installed correctly by opening command prompt and run ‘hexo’.

Once installed, create a folder and initialise Hexo in that folder. By default you will get the Landscape theme.

1
$ hexo init

Install hexo dependencies.

1
$ npm install

Run the hexo server to browse your blog on http://localhost:4000/.

1
$ hexo server

Create a new post

Very easy to create a new post

1
$ hexo "Hello World"

Hexo creates a Mark Down file with a Title, Date and Tags. Now your file is ready for your content.
Check out the Hexo documentation on how to use and change the header settings.

When you’ve create your ‘Hello World’ post and you are ready to deploy to the server, I run the ‘Generate’ command.

1
$ hexo generate

It creates the output in the Public folder eg:

Run the hexo server and test your blog.

There are many more commands you can use. I only covered the ones I use regularly.

Publish to AWS S3 bucket via AWS CLI tool

Easiest way to publish files to your S3 bucket is to login to AWS, go to S3 services, use the web UI ‘Actions’ button and select ‘Upload’.
After a while it becomes a tedious repetitive job, so I researched their CLI tools - AWS CodeCommit Command Line Reference.

First download and install AWS CLI tool - Install instructions on AWS CLI tool.
When successfully installed, configure security keys.

** Configure Security Keys for CLI tool **
Go to AWS IAM (Identity and Access Management) manage console. This is where you create users and groups.

I’ve created a new user. The new user has a Access Key ID and a Security Access Key.
Keep the Secret Access Key safe as it is used for authentication to S3 buckets.

I also created a group with a Permission Policy of AmazonS3FullAccess and added the user to that group.

Then, open a command prompt and run to configure CLI security

1
2
3
4
5
6
aws configure

AWS Access Key ID [None]: AKIAJJTLECQSBDU53CGA
AWS Secret Access Key [None]: yFbx4erWVEB4zlw9uHelHqbMo6aekmtSsoGeyDcC
Default region name [None]: {YourRegionName}
Default output format [None]: json

AWS Access Key ID [None]: Type your target AWS access key ID here, and then press Enter
AWS Secret Access Key [None]: Type your target AWS secret access key here, and then press Enter
Default region name [None]: Type us-east-1 or any other region here, and then press Enter
Default output format [None]: Type json here, and then press Enter

You can test your config with ‘ls’ command. This should list all your buckets on S3.

1
aws s3 ls

Resource - Configure the AWS CLI

** Create batch file to run CLI with ‘sync’ command **
I use the ‘sync’ command to copy and update my content.
This is what I execute in the batch file

1
aws s3 sync . s3://ruanbeukes.net --delete --exclude ".vscode/*" --exclude ".git/*" --exclude "*.bat"

AWS CLI is now configured.

Resource - CLI Commands.

Create CloudFlare Account for Secure Browsing and more

I use CloudFlare for these reasons:
     1. Map my domain name to my S3 bucket endpoints
     2. Secure browsing
     3. It free! :)

The outcome of the CloudFlare setup: https://ruanbeukes.net instead of http://ruanbeukes.net.

Create a CloudFlare account.

** Map S3 bucket endpoints **
Look for the Add Site menu option and add your domain name and let CloudFlare do its magic.

Add your S3 endpoints. Here is my setup…

CNAME with my domain name S3 bucket endpoint.
CNAME with my sub domain S3 bucket endpoint.

** Secure browsing **
Next, go to the Crypto menu section and make sure your SSL is set to Flexible.

Then, create a Page Rule to force HTTPS only.

The last bit is to update your domain registrar Nameservers with the two assigned to you from CloudFlare.

One thing you need to understand about these changes - BE PATIENT! I was chasing ghost a few times because of my impatience. CloudFlare suggests up to 24hrs for settings to take effect.
In my case it was 2-3 hours. But still, I could have gone to bed earlier :)

So…when you’ve waited patiently, test your domain URL the next day or you can use WhoIsHostingThis to make sure hosting is done by CloudFlare.

CloudFlare has a CDN (Content delivery network) feature and will request my blog content from the S3 bucket.
This is the reason why the host is CloudFlare instead of Amazon.

Resource - How do I use CloudFlare with Amazon’s S3 Service?.

Done

In my next post I’ll cover adding comments to your Hexo blog.

Resources

AWS Docs

Hexo Docs

CloudFlare

Pluralsight
I would highly recommend Jeff Ammons Pluralsight course on Build a Better Blog with a Static Site Generator.
It is a goodie for Hexo customisation and other gold nuggets.

Thanks Jeff, keep up the good work!

It's Alive!!!

Welcome to my blog

I am happy to announce that my blog is finally up and running.

I’m one of those guys that Scott Hanselman would refer to as “The Dark Matter Developer”.

Well…this ends today.

I’m getting out of my comfort zone to explore a different side of me.
How else will one grow if one is not willing to try new things?

What this blog is about

I’m a Software Developer and my focus will be on web technologies.
Although I love C# and .NET, there are many other server side technologies one can use.
I believe you should use the right tool for the right job and I will share anything related to the web world.

More important, I will share my experiences.

Anything from a new cool tool I discovered and found very useful, to a really stupid thing I’ve done…

Hopefully those posts will be in the minority :)

We all make mistakes and if you can learn from mine, I’ve succeeded. Less bugs, better software.

So that’s it, my blog is officially alive.

In my next post I will share my incredible journey of building my blog.