Eli Weinstock-Herman

AngularJS vs Knockout - Modules and DI (6 of 8)

Original post posted on Monday, October 14, 2013 at LessThanDot.com

I'm reviewing Angular and Knockout to determine which would fit better for a variety of upcoming projects. The larger or more complex a project, the more important it is to be able to modularize the code. Modules provide organization, ensure script loading order is correct, and enable dependency injection for cleaner unit testing. AngularJS brings a built-in ability to define modules and inject dependencies. With Knockout, we'll look at using RequireJS, and AMD packages.

This is the sixth of eight posts looking at the capabilities of knockout and Angular. In the introduction post, I outlined the capabilities that I am evaluating for. In the fifth post, I looked at templating. This post explores defining modules and dependency injection.

All of the examples presented throughout the series are available in the tarwn/AngularJS-vs-Knockout repository on github.

Modules in AngularJS

The ability to define modules is built into AngularJS, but it doesn't include an asynchronous loading option, so when we get to that point we'll be including the external library script.js.

AngularJS Simple Dependency Injection Example

Full source available at Angular/SimpleDI.html.

Angular modules are collections of functionality with a shared configuration block. Controllers, services, and even values can be attached to the module and will be loaded with the module. Modules can have dependencies on one another, which affects their loading order. Controllers and Providers (services, factories, etc) list their dependencies when they are defined, and those dependencies are filled by Angulars injector.

Here is an example based on the earlier sample for data binding, except our controller has a dependency on the "sampleServices/ListOfItemsService" service and when we push the button it will call this service to obtain the list of items to be displayed.

Code: html
<html ng-app="sampleApp">
<head>
    <!-- ... -->
    <script type="text/javascript" src="js/lib/angular-1.0.8.min.js"></script>
</head>
<body>
<div ng-controller="ModuleDIController">
   
    Text Value: {{ textValue }}<br />
    List Of Items: <ul ng-repeat="item in listOfItems">
                        <li>{{ item.number }} - {{ item.name }}</li>
                   </ul><br />
 
    <input type="button" ng-click="fillItems()" value="Call Service"/>
</div>

And the module, controller, and service:

Code: javascript
var sampleServices = angular.module('sampleServices', []);
sampleServices.service('ListOfItemsService', function () {
    this.getList = function () {
        // pretend call
        return [
            { name: "first value", number: 123 },
            { name: "second value", number: 456 },
            { name: "third value", number: 789 }
        ];
    };
})
 
// and a module that has a controller that depends on the ListOfItemsService
var sampleApp = angular.module('sampleApp', ['sampleServices']);
sampleApp.controller('ModuleDIController',
    ['$scope', 'ListOfItemsService',
    function ($scope, listOfItemsService) {
        $scope.textValue = "some text";
        $scope.listOfItems = [];
 
        $scope.fillItems = function () {
            $scope.listOfItems = listOfItemsService.getList();
        };
    }]
);

As part of the example I've listed both the dependencies ($scope and ListOfItemsService) and then defined the controller function to take these two properties. Angular has the ability to infer dependencies based on the name of the parameters or list them explicitly like this. The explicit method is safe for a wider range of minification programs, so I decided to try it out.

One advantage to working with modules like this is that we can define things like these services independently from the logic that is going to use them, then let the library (AngularJS in this case) figure out how to wire the pieces back together again. This takes a lot of complexity and extra work out of our hands, because we no longer have to deal with juggling a long list of includes or functions into the right order. It also keeps the root namespace clear and makes it simpler to see what external dependencies a chunk of code is using. We can also swap out the provider, replacing it with one that has different functionality, caching, or stubs it out for testing purposes.

AngularJS Errors in Modules

Full source available at Angular/ModuleError.html.

The next thing I'm concerned with is how errors will look once the framework has wired together my dependencies. Instead of returning a list of items, I'm going to modify the service to throw an explicit error and see what the result looks like.

Code: javascript
var sampleServices = angular.module('sampleServices', []);
sampleServices.service('ListOfItemsService', function () {
    this.getList = function () {
        throw new Error("Error occurred, do we know where?");
    };
})

In Chrome I get a clean stack trace with clickable references to the files and correct line numbers, Firefox gets the same information but it's jumbled with extra characters (possibly intended specifically for chrome output?) and the whole error is a single link that simply expands to show some properties that aren't very useful. This is less than great for an unexpected error during development, but it also means business as usual for expected production errors, as they'll be able to use some standard error handling code instead of something new.

AngularJS Module Loading

Full source available at Angular/AsyncDI.html.

By default, Angular does not include a method for loading scripts. The documentation for module indicates that there are external libraries that solve this, but don't indicate what they are. Searching for something I could use as an example, I ran into a post (and didn't copy down the URL, sorry) that uses require.js and a mention by someone that they had used script.js. I also ran into a post (Building Huuuuuge Apps with AngularJS) by one of the core Angular team members, Brian Ford, that recommends against using RequireJS with Angular, so I suppose script.js it is.

Using script.js, I remove the ng-app attribute and instead bootstrap the document to use the sampleApp module once the two files are loaded.

Code: html
<html>
<head>
    <!-- ... -->
    <script type="text/javascript" src="js/lib/angular-1.0.8.min.js"></script>
    <script type="text/javascript" src="js/lib/script.js"></script>
</head>
<body>
<div ng-controller="ModuleDIController">
   
    Text Value: {{ textValue }}<br />
    List Of Items: <ul ng-repeat="item in listOfItems">
                        <li>{{ item.number }} - {{ item.name }}</li>
                   </ul><br />
 
    <input type="button" ng-click="fillItems()" value="Call Service" />
</div>

And the javascript section of the page is reduced to defining the path that the scripts should be loaded from, and the bootstrap code that replaces the ngApp directive.

Code: javascript
$script.path('js/AsyncDI/');
$script(['sampleApp', 'sampleServices'], function () {
    angular.bootstrap(document, ['sampleApp']);
});

The downside of this method is that it requires me to list all of the files I want to load, which is easy when you have a couple files, but is going to be nasty when I start getting past about 10-20 files (and I imagine it will be real nasty if I have hundreds of files).

Modules in Knockout

Once again we're looking at a feature that is outside the scope of Knockout, so it's time to turn to a 3rd party library. The most popular choice here seems to be RequireJS.

Knockout/RequireJS Simple Dependency Injection Example

Full source available at Knockout/SimpleDI.html.

Using the simple databinding example as a base, here is an example of using RequireJS modules to define a module and a service in seperate modules, then using require to ensure the module with the viewmodel is loaded prior to instantiating and binding it:

Code: html
<html>
<head>
    <!-- ... -->
    <script type="text/javascript" src="js/lib/knockout-2.3.0.min.js"></script>
    <script type="text/javascript" src="js/lib/require-2.1.8.min.js"></script>
</head>
<body>
<div>
    Text Value: <span data-bind="text: textValue"></span><br />
    List Of Items: <ul data-bind="foreach: listOfItems">
                        <li data-bind="text: number + ' - ' + name"></li>
                   </ul><br />
    <input type="button" data-bind="click: fillItems" value="Call Service"/>
</div>

And the javascript driving the form:

Code: javascript
define("sampleServices/ListOfItemsService", function () {
    // return an object literal for the service object
    return {
        getList: function () {
            return [
                { name: "first value", number: 123 },
                { name: "second value", number: 456 },
                { name: "third value", number: 789 }
            ];
        }
    }
});
 
// and a module that has a controller that depends on the ListOfItemsService
define("sampleApp/ModuleDIModel",
    ["sampleServices/ListOfItemsService"],
    function (listOfItemsService) {
        // return a constructor for the model
        return function () {
            this.textValue = ko.observable("some text");
            this.listOfItems = ko.observableArray([]);
 
            this.fillItems = function () {
                this.listOfItems(listOfItemsService.getList());
            };
        }
    }
);
 
// use require to define dependencies to start and bind viewmodel
require(["sampleApp/ModuleDIModel"],
    function (ModuleDIModel) {
        var viewmodel = new ModuleDIModel();
        ko.applyBindings(viewmodel);
    }
);

Each define block has 3 arguments, the name of the module, the array of dependencies it requires, and the function it executes when initially resolved. The require block at the bottom then lists some requirements for inline execution of the included function and executes the function when they are available.

Like the Angular example, the service module will return a single instance of the ListOfItemsService that will be used by anyone needing it, while the ModuleDIModel returns a function constructor for the viewmodel. When I tell RequireJS I require the ModuleDIModel, it automatically resolves the dependency on the ListOfItemsService module.

Knockout/RequireJS Errors in Modules

Full source available at Knockout/ModuleError.html.

Once again, my next concern is whether troubleshooting errors will be impaired. Substituting an error for the return of the ListOfItemsService again:

Code: javascript
define("sampleServices/ListOfItemsService", function () {
    return {
        getList: function () {
            throw new Error("Error occurred, do we know where?");
        }
    }
});

In Chrome, the console shows the error with a short stack trace which, when clicked, takes me directly to the line that produced the error. In Firefox, the console shows both the error message and the offending line, which opens up the source to the correct spot when clicked.

Again, this is primarily only going to be an issue during development, a I will hopefully have appropriate error handling logic in the application for expected errors in production (and I don't expect my users to troubleshoot them for me).

Knockout/RequireJS Module Loading

Full source available at Knockout/AsyncDI.html.

RequireJS is built specifically for asynchronous module loading, so where we only have module level injection, instead of controller level in AngularJS, we do already have the asynchronous module loading of RequireJS. To switch from inline scripts to asynchronously loaded ones, all we have to do is make a few changes:

Code: javascript
require.config({
    baseUrl: 'js/AsyncDI'
});
 
require(["sampleApp/ModuleDIModel"],
    function (ModuleDIModel) {
        var viewmodel = new ModuleDIModel();
        ko.applyBindings(viewmodel);
    }
);

Like the AngularJS exmaple, I define a base URL for the library and then relay on it to run my bootstrapping code. In this case the bootstrap code hasn't changed, though. RequireJS now automatically looks for js/AsyncDI/sampleApp/ModuleDIModel.js and resolves it's dependencies too, then runs my method.

Some Differences

Where do I start? Most of this post was a comparison of external libraries, so while it was hopefully useful, I'm not going down a script.js vs RequireJS path (you can check this out instead: $script.js vs RequireJS: Dependency Management Comparisons).

In fact, there is really only one difference we can look at here:

It isn't built in

In the case of Knockout, we once again had to pull in an extra package. As always, this means we now have to track and manage yet another library that has separate security vulnerabilities, updates, and support team from the core library. It's a tradeoff and you have to decide whether it's worth it or not.

It isn't built in

In the case of Angular, Modules and dependency injection are built in, but they suggest either keeping everything in a few files or merging them with a DIY build process. In my book, 1000+ line files are a code smell and I can't stand them, but I think what it comes down to at the end of the day is whether you are ok with big files, want to use an external library (like script.js or RequireJS), or want to merge and minify them in your build process (you do have a build process, right?).

I'd compare RequireJS + Knockout to Angular, but when we included RequireJS we not only got modules, we also got asynchronous loading, so then we had to ratchet Angular up a notch and add script.js to get back to the same spot.

Final Thoughts

In both cases there is one unanswered issue. When is your application too big for keeping it all in a couple files? When does it become so large it impacts the user's ability to load it via script tags, impacts our ability to keep our dependencies straight, etc. I think 10 - 20 modules is not too bad to keep track of as separate files, but I wouldn't want to go larger than that. I could easily use namespacing with Knockout instead of a module loader for something that small, though it's not as clean when it comes time to unit test (that post is coming, don't worry).

So what is big? When do we need to switch from a few files or one big minified file to a module loader or a bundler like Cassette that can auto bundle everything based on include comments? Is there a sweet spot in between where just have modules and DI without asynchronous loading is perfect? Or is asynchronous loading for "huge" applications (defined by Brian Ford above as tens and hundreds of thousands of lines) totally unnecessary?

I don't have those answers, but they are important considerations we should be making when we sit down and try to choose between AngularJS and Knockout on a future project.

I think the AngularJS DI system works really well, and I was really happy about how it performed for the Unit testing examples (next post). I'm not crazy about the inferred method for DI, I feel like it saves a little typing while limiting minification and potential asynchronous loading options.

When I first looked at RequireJS for defining modules (and at Angular's method, to be honest), it looked messy. They both still look a bit messy, but after working through these examples, I've decided I do actually like it and I'm planning on retrofiting some past projects to use RequireJS. And, unlike the build your own file merger suggestion for Angular, it already has well documented tools and methods for optimizing via your build process.

So, not a strong conclusion or set of comparisons for this post, but I'm sure we can hash it out in the comments :)

Knockout vs AngularJS

Comments are available on the original post at lessthandot.com