Last Updated: February 25, 2016
·
1.347K
· alextercete

Testable Controller Activate in AngularJS

I've adopted John Papa's AngularJS style guide recently, and it's been working great for me so far. One of its rules I don't like is the Controller Activation Promises:

function AvengersViewModel(dataService) {
    var viewModel = this;

    viewModel.avengers = [];
    viewModel.save = save;

    activate();

    function activate() {
        return dataService.getAvengers().then(function (data) {
            viewModel.avengers = data;
            return viewModel.avengers;
        });
    }

    function save() {
        // (...)
    }
}

I like the fact that the activation code is in its own function, but invoking it straight away is pretty much the same as having logic in the constructor. This means that if you want to unit test the method save you'll need to make sure that dataService.getAvengers is stubbed properly.

I wanted to expose activate and have it called by someone else rather than the AvengersViewModel:

function AvengersViewModel(dataService) {
    var viewModel = this;

    // (...)

    // Expose it
    viewModel.activate = activate;

    function activate() {
        // (...)
    }

    // (...)
}

But who could that someone be?

The naive approach

I knew about ngInit, but I also knew I was not supposed to use it. That didn't stop me from trying:

function avengers() {
    return {
        scope: {},
        template: '<div ng-init="viewModel.activate()">The avengers directive</div>',
        controller: 'AvengersViewModel',
        controllerAs: 'viewModel'
    };
}

That worked, but it didn't look right.

A better approach

I remembered seeing someone using require to make the directive's own controller available as the fourth parameter of the link function. Apparently, you can even omit the require property altogether, and you'll have access to the directive's controller instance:

function avengers() {
    return {
        scope: {},
        template: '<div>The avengers directive</div>',
        controller: 'AvengersViewModel',
        controllerAs: 'viewModel',
        link: function (scope, element, attributes, viewModel) {
            viewModel.activate();
        }
    };
}

Now I can properly unit test my view models and have the benefits of Controller Activation Promises. Awesome!

Have a fresh tip? Share with Coderwall community!

Post
Post a tip
Did you know?

AngularJS pages are long-lived browser apps with the same script risk as a newer single-page app. Third-party code in the template can read forms and values rendered on the page.

Security insights from Cside