AngularJS: $onInit component hook and simplifying controller tests

Radek Anuszewski
Frontend Weekly
Published in
4 min readMay 7, 2016

UPDATE 2017–02–13:

While $onInit is still very useful hook (as described in this post), I realized there are things which it can’t deal — when you need to react on changes in parent component. When these situation occurs? When user interaction happens or data are loaded asynchronously. I wrote 2 posts about it:

  1. Angular JS $onChanges component hook use case: switching between Youtube and Vimeo players based on URL
  2. AngularJS: $onChanges component hook as solution for not ready bindings

Especially the second one is important, as asynchronous loading is common thing (loading options for select or loading element to edit view, when you load it inside edit component based on route parameters).

But $onInit is not dead — so I wish you good reading!

Complete mess in Angular JS controllers

Placing method definition inside constructor

We used to write our controller like this:

function OrderController() {
var vm = this;

vm.orders = [];

vm.save = function() {
//implementation
};
vm.delete = function() {
//implementation
};
vm.loadOrders = function() {
//implementation
};
}

Quite apart from fact that we can use Prototype here (due to fact that controllers in Angular are instantiated and can be treated like normal JS “classes”) to clear it and make it more similar to Typescript classes (widely used in Angular 2), often mess in Angular controllers is made also by:

Placing initialization logic inside constructor

We also write code like this:

function OrderController() {
vm.loadOrders = function() {
//implementation
};
vm.loadOrders.then(function (orders) {
vm.orders = orders;
});
//other stuff
}

Of course any work in constructor is a smell, but the true is we had to. We had to and now we don’t have to — because Angular 1.5.3 components come with handy lifecycle hooks: $onInit(), $onChanges(), $onDestroy() and $postLink() described widely by Pascal Precht. And now we know where is place for any work, which we used to place in constructor before.

Simplifying unit tests with $onInit hook

$onInit component hook

AngularJS’s component hook was described many times, including Todd Motto in $onInit and new “require” Object syntax in Angular components and Aviv Ben-Yosef in Angular’s .component — what is it good for?, from me I want to add that it really can simplify unit tests and code refactoring.

Mocking in tests can be made completely unnecessary in some cases

Sometimes we just screw up our code, or we make solution which development takes just a few minutes and we annotate it with TODO comment.

In my company we had to check whether host name provided by user is correct or not. The simplest solution was creating new function in AngularJS controller:

function Controller() {
vm.checkHostname = function(hostName) {
//implementation
return isValid;
};
}

Host name could be IP address in LAN like 192…, “regular” web page like www[dot]something[dot]com, or computer name in LAN like Predator-PC. Also it has to accept localhost, for development purposes. So, logic was quite complicated and easy to brake, especially that we are not an experts in Regular Expressions in Javascript which we used for validation.

When we decided to move it to its own file (as Strategy design pattern) as a preparation for moving it to Angular custom validator, we had to write some unit tests, preferable with explicit creation of our Controller (with new() operator), cause thing under test was Angular independent in 100%, it just took host name and returned boolean — we wanted full isolation. Unfortunately, dependencies like $http, our own wrapper for local storage and others were injected directly to controllers and were invoked in constructor, like:

function Controller($http, LocalStorageFacade, ManyOtherThings) {
vm.loadParameters.then(bindParameters.bind(this));
LocalStorageFacade.loadUser().then(something.bind(this));
//etc...
}

So, we had to mock all this dependencies (we use QUnit for our tests, because it’s simple and doesn’t require any configuration):

QUnit.test( "IP like 192.168.1.1 should be valid", function( assert ) {
var fakeHttp = createFakeHttp();
var localStorage = createFakeLocalStorage();
//many things like this
var controller = new Controller(fakeHttp, localStorage, ...)
assert.ok(controller.isValid('192.168.1.1' === true));
});

And things like this can take some time, and it’s frustrating especially if they are not needed in test.

With $onInit component hook, we can move it all initialization logic to this hook:

function Controller($http, LocalStorageFacade, ManyOtherThings) {
vm.$onInit = function () {
vm.loadParameters.then(bindParameters.bind(this));
LocalStorageFacade.loadUser().then(something.bind(this));
//etc...
}
//No need of any initialization logic!
}

So dependencies for our test can be simply empty objects, and test would look like:

QUnit.test( "IP like 192.168.1.1 should be valid", function( assert ) {
// no need for any mocks!
var controller = new Controller({}, {}, ...)
assert.ok(controller.isValid('192.168.1.1' === true));
});

Conclusion

I think it could save a lot of time, because we provide mocks for system under test only when it really needs them. It could also improve performance of tests, which is a premature optimization if test suite in small, but if we have thousands of tests, maybe it matters. With $onInit hook syntax is much cleaner and helps us to focus on things we need to be focused.

--

--

Radek Anuszewski
Frontend Weekly

Software developer, frontend developer in AltConnect.pl, mostly playing with ReactJS, AngularJS and, recently, BackboneJS / MarionetteJS.