Use KnockoutJS Modules in AngularJS

Posted By Rajan Rawat | 11-Apr-2017

Using KnockoutJS View Model in  an AngularJS App

 

Angular 1.3 has a ngModelOptions directive that gives you the some flexibility in the way ngModel does in the databinding. The new and the interesting option is getterSetter, that allows the angular model is actually a getter/setter function

 

Before we start and get into the angular over a year ago, i does the frontend framework with knockout. But the knockoutJs models view having the observables, observables array and computeds, they all are implemented as getter/setter. But when i saw angular has added a support for binding the getter/setter. Then i realise i should do this and see i can use knockoutJS modules in AngularJS

So the question is can we use knockout view model without modifications in angularjs app and the answers is yes and i am pretty sure if there is any real-world uses, it will be definitely and interesting exercise

So lets began with a demo of shopping cart. Its a shopping cart which have observables, observables arrays and computeds

ther are cart and cartline view models from knockout app and we will reuse in angular app  




function formatCurrency(value)
    return "$" + value.toFixed(2);
}
 
var shoppingcartLine = function() {
    var self = this;
    self.category = ko.observable();
    self.product = ko.observable();
    self.quantity = ko.observable(1);
    self.subtotal = ko.computed(function() {
        return self.product() ? self.product().price * parseInt("0" + self.quantity(), 10) : 0;
    });
 
    self.category.subscribe(function() {
        self.product(undefined);
    });
};
 
var shoppingcart = function() {
    var self = this;
    self.list = ko.observableArray([new shoppingcartLine()]);
    self.grandTotal = ko.computed(function() {
        var total = 0;
        $.each(self.list(), function() { total += this.subtotal() })
        return total;
    });
    self.addLine = function() { self.list.push(new shoppingcartLine()) };
    self.removeLine = function(line) { self.list.remove(line) };
    self.save = function() {
        var dataToSave = $.map(self.list(), function(line) {
            return line.product() ? {
                productName: line.product().name,
                quantity: line.quantity()
            } : undefined
        });
        alert("Could now send this to server: " + JSON.stringify(dataToSave));
    };
};

Now if we want use Angular here we have to create module and controller . The controller have to be simpler, all we have to do is new up the Shoppingcart View Model and list of the products will be same as in the knockout demo app.



angular.module('Shoppingcart', [])
    .controller('ShoppingCartController', CartController);

function CartController() {
    this.cart = new ShoppingCart();
    this.sampleProductCategories = sampleProductCategories;
}

 

Now the original shopping cart with knockout Bindings





  <tbody data-bind='foreach: list'>
            <tr>
                <td>
                    <select data-bind='options: sampleProductCategories, optionsText: "name", optionsCaption: "Select...", value: category'> </select>
                </td>
                <td data-bind="with: category">
                    <select data-bind='options: products, optionsText: "name", optionsCaption: "Select...", value: $parent.product'> </select>
                </td>
                <td class='price' data-bind='with: product'>
                    <span data-bind='text: formatCurrency(price)'> </span>
                </td>
                <td class='quantity'>
                    <input data-bind='visible: product, value: quantity, valueUpdate: "afterkeydown"' />
                </td>
                <td class='price'>
                    <span data-bind='visible: product, text: formatCurrency(subtotal())' > </span>
                </td>
                <td>
                    <a href='#' data-bind='click: $parent.removeLine'>Remove</a>
                </td>
            </tr>
        </tbody>
 

Now when we change it into the Angular





<tbody>
    <tr ng-repeat="line in vm.shoppingcart.lines()">
        <td>
            <select ng-options="category.name for category in vm.sampleProductCategories"
                    ng-model="line.category" ng-model-options="{ getterSetter: true }">
                <option value="">Select...</option>
            </select>
        </td>
        <td>
            <select ng-options="product.name for product in line.category().products"
                    ng-model="line.product" ng-model-options="{ getterSetter: true }"
                    ng-show="line.category()">
                <option value="">Select...</option>
            </select>
        </td>
        <td class='price'>
            <span ng-bind='line.product().price | currency'></span>
        </td>
        <td class='quantity'>
            <input ng-show="line.product()" ng-model="line.quantity" ng-model-options="{ getterSetter: true }" />
        </td>
        <td class='price'>
            <span ng-show="line.product()" ng-bind="line.subtotal() | currency"></span>
        </td>
        <td>
            <a href='#' ng-click="vm.cart.removeLine(line)">Remove</a>
        </td>
    </tr>
</tbody>

 

Knockout binding Angular equivalent
foreach ng-repeat
options ng-options
value ng-model with getterSetter:true
visible ng-show
formatCurrency (custom fn) currency filter (built-in)
click ng-click
 

So the now the new Angularjs app works exactly like the KnocoutJS App. If you want see the more complex view Model which make rest API calls for examples. So definitely you have to do extra work to make sure Angular digest cycle is triggered properly.

Request for Proposal

Recaptcha is required.

Sending message..