More thoughts on Knockout.js and JavaScript MVVM

Today I want to share with my reflections on using Knockout.js and client side MVVM pattern. Some of them refer to my previous post on the same topic (in Polish language). These are my advices to all entusiasts of JavaScipt rich applications, especially to those, who want to try Knockout in production environment.

Layered structure

Most of the frontent application logic, I’ve got an occasion to look into, was pretty messy. Simple web pages usually are build of chunks of JavaScript code used to bound existing libraries and perform simple logic tasks. Everything is coupled, any kind of logic and DOM manipulations are mixed together. This is the place, where unicorns live and magic happens. But if you’re into rich client side applications and frameworks such as Knockout.js, this is not an option. Why?

Knockout.js, AngularJS, Backbone and other MV* frameworks are dedicated to create a web applications. This literaly means, that your client page is an application rather than an ordinary page, rendered only to display the data. There will be probably a lot of code and logic bound to HTML on the page. If you will manage your code in unstructurized manner, you’ll soon find yourself in spot, where any further changes will the a depressing, time-consuming nightmare.

To be able to manage a large chunks of code, you will need to decouple your JavaScript logic into layers. Beacuse Knockout doesn’t provide any application layers off hand, responsibility of creating them lies on you. In rest of the post bellow I want to share my personal experiences developed during many months spent of using Knockout in production environment. Layers described by me can be exactly mapped onto specific patterns/groups of patterns and are described from top to bottom, beginning from layer used for server communication, through data management, HTML page partitioning to core used to bound everything together.

Gateway

The main goal of the Gateway object is to abstract server API to look like an ordinary JavaScript function. We would like to call the service in form of usersApi.getUserById(1) rather than cryptic $.post(''api/users', { id: 1 }). In my opinion this is more natural and gives us an easier way for further refactoring. What else can gateways do?

First of all, they should be able to hide necessity of resolution parametrized URLs from view models. They should be able to resolve them or have some helpers attached (think about eqivalent of UrlHelpers, path methods etc.).

In most of the cases I’ve met, server communication is done by traditional ajax requests through jQuery ajax or similar calls. Since those functions always returns jQuery promises as a result, you may find that this is not what you want. Maybe you need a more detailed informations or a custom wrapper around promises. Or if you use RxJS, you probably may want Rx.Observable objects. For all of these cases Gateway is good place to implement.

Gateways could be also used to perform mapping of JSON into fully qualified JavaScript objects you may want to use. This includes things like serializing/deserializing dates, adding additional computed properties, adding/removing recursive dependencies or even changing entire structure of object to beter fit our needs. From Knockout point of view, we can wrap/unwrap objects into ko.observables directly at this layer.

When your data manipulation and mapping logic become more and more complex, consider using a dedicated Factory/Mapper objects to maintain code decoupling.

Data source

Data sources (or Data Access Objects) are alternatives to gateways, since they serve similar purpose, but provide much more complex mechanisms. When we speak about data represented on the page, we’ll probably see problems associated with incomplete data representation on client side. Paginated tables, autocomplete suggestions, calendar pages, infinite scrolls etc. When used with remote source, all of these use server side requests to get data to be shown to end users. But on large data volumes we never retrieve all of the informations, instead using only those records, which need te be displayed. One of the roles of data sources is to store and manage data received this way, so the next time user goes back to previously displayed page or gets subset of records already provided, we may restore it from the local cache rather than repeating request to the server.

If you want to see a possible ways to implement this pattern, I would recommend you to look at some of the existing implementations, like:

All of them provide interesting point of view of what separated data source managers are capable to.

Among other advantages of data source separation we could mention sharing of common data between view models, which should not reference too each other directly, but also managing data objects lifecycle. Fetching data from the server, and synchronizing changes made localy by the client could be done easily this way.

When it comes to data objects, I’ve noticed that for purposes of data manipulation on the client side it’s nice to have some kind of universal identifiers to manipulate enities. Those identifiers should uniquely identify single entity among all others on the page – this means, that database identifiers from the JSON object representing server side entities may not be sufficient, since in many cases the records id’s (at least when we speak about relational databases with non-UUID primary keys) are guaranted to be unique only in single table/collection scope.

In my case good solution of this problem seems to be an additional object property – lets call it $id – which serves only client side logic (server may ignore that value) and it’s combination of unique identifier of the entity type and single entity identifier. Since it’s called the same among all data objects in client side application, no matter of their type, we may also definie universal comparison operators and function for fetching and data integration. To do so it’s also necessary that $id has to be generated in deterministic way – no matter how many times we create a JavaScript representation of the same database record, it should allways have the same $id value.

Partial View Models

Partial view models are used to separate model-view binding in domains of interests. Instead of unorganized bindings of all observed properties to single God Object, remember to use smaller pieces which actually fits a concrete parts of your page – menus, navbars, forms, modal windows etc.

Don’t ever mix a multiple view models in scope of single view component. The only exception from that rule is when you need to manipulate current view model in it’s parent context, but even in that case you shouldn’t access higher than direct parrent of current VM. If you do, that usually means, something is wrong.

Avoid referencing between view models, especially when they share the same hierarchy level. View models should be an independent units of work. They don’t need to know details of other view models to perform requested operations. Use Knockout contexts or dedicated objects as an inter-communication layer and share them between view models, when some message passing is needed.

Don’t create large nested objects inside view models. Inject them through constructors instead. Knockout view models lifetime is much longer than AngularJS controllers, but it’s still a better solution to share large objects and – if they come with a lot of logic – to decouple them from view model. This also includes objects used to communicate with external world.

Application root

Since Knockout allows you to use only one View Model as root for whole page, my advice is to create dedicated view model, which won’t be used with any visible parts of the page itself. It’s role is to serve as a container for actual view models used to display data on the page and to provide universal features to be used by everyone. For me this option remains true even for small ingerention of Knockout logic into the page, because in the world of changing requirements formally small pages may grow rapidly in very short amount of time.

When comes to integration of partial view models and application root, usually it’s sufficient to create all partial VMs on root level. View models can be nested in large tree structures, but don’t overuse this feature. Try to keep your view models dependency scopes as flat as you can. Application root is a good point to handle inner components creation and lifecycle. That also means, it should be able to access and provide all objects – gateways, data sources, factories, event buses, AJAX handlers etc. – directly to view models.

Appendix: Events and Pub/Sub pattern

There are few more things I wish to present in this post. One of them is a Publish/Subscribe pattern and event processing. It isn’t bound to any specific layer since all of them can get advantage of using it. I also found it the best way of dealing with communication between various Knockout view models.

Below I will show one of the simplest implementation used to handling custom events. If you want to be able to use more advanced techniques, you probably want to look for some dedicated libraries such as RxJS.

var EventStream = function(){
    this._channels = {};    // this could be made private

    // subscribe to channel
    this.$on = function(channelName, callback){
        if(typeof callback === 'function'){
            var channel = this._channels[channelName];
            if(!channel)
                channel = this._channels[channelName] = [];
            channel.push(callback);
        }
    };

    // unsubscribe from channel
    // this is tricky since anonymous functions, once subscribed cannot be removed
    this.$off = function(channelName, callback){
        var channel = this._channels[channelName], i;
        if(channel && (i = channel.indexOf(callback)) > -1){
            channel.splice(i, 1);
            if(!channel.length)
                delete this._channels[channelName];
        }
    };

    // publish an event
    this.$trigger = function(channelName, event){
        var channel = this._channels[channelName];
        if(channel)
            for(var i = 0; i < channel.length; i++){
                try {
                    channel[i](event);
                } catch(e){
                    // an error occurred in subscriber function
                }
            }
    };
}

Since we have whole event pub/sub feature in one separate prototype, we can now implement it in any object we wish, either through composition or as a mixin. Additionaly we can use globaly shared instances of EventStream in similar fashion we use an event buses. For Knockout applications there is also an alternative in form of knockout-postbox.

Final thoughs

I want to end with a few short advices:

  • All of my observations are based on observations of using Knockout and MVVM in non-SPA paradigm. As I’ve said in previous posts, I wouldn’t recommend Knockout for creating single page applications, because there are a lot of alternative frameworks, which suits better for this purpose.
  • Knockout-validation library – I’ve found it pain in the ass almost everytime, I’ve needed to create anything more complex than basic, flat structure validation. There are a lots of problems when it comes to nested objects validation, nullable objects and dealing with validation results caches – which btw. cannot be accessed via library API. So if you are determined to use it, consider yourself warned.
  • Use unit tests any time you can. This not only will allow you to catch more potential errors (which are more likely to exist in language such as JavaScript) but are also a nice way to check if your code blocks are properly separated. And better prepare yourself, because client side logic tests are much more difficult to write than their backend cousins.
  • Create a common prefix for properties used as helper fields and methods, but not directly responsible for view model page manipulations. I’ve already shown examples such as $id field and event stream methods with $ prefix. I’ve found them quite usefull, but I don’t want refer to them from view data-bind attributes or server side. They should remain transparent for both of these.
  • I didn’t mention about any kind of Service layer, used for performin business logic. The reason is simple – unless you’re creating SPA with offline editing capabilities, due to reliability and security reasons you probably never wan’t to inject a business logic into frontend.

And last but not least. There are no universal rules. Form your own point of view, appropriate to your problems.