Building Modern UIs with AngularJS and Knockout in SharePoint

Introduction

In the article titled "Accelerate SharePoint Web UI Development with AngularJS," we saw how AngularJS can be integrated with the SharePoint framework. In this article, we will compare the two popular frameworks with SharePoint. You will get an opportunity to compare one with the other.

Overview

AngularJS is based on the MVC pattern and its conceptually is based on Model, View Controller, Template, Directives, Dependency Injection, Data binding, Modules, Services, and so forth, to name a few. Knockout is an MVVM pattern and conceptually based on Observables, data-binding declaratively, and templates.

Let's understand how both work with the help of an example. We will implement the same solution in both the frameworks. The UI of the functionality we are trying to build is shown in Figure 1:

SP2-1
Figure 1: The UI of the functionality we are building

The solution will get the data from a library and display them on browser in a custom UI. The user will be able to interact with the documents by performing a "Like"/"Unlike"action. The HTML, script, and CSS pages are uploaded in the site assets library in the respective folders, as shown in Figure 2.

SP2-2
Figure 2: The files have been uploaded in the site assets library in the respective folders

Create a web part page in the 'Site Pages' library and add a content editor web part to the page. In the tool part of the content editor web part, specify a relative URL of the HTML file uploaded in the SiteAssets\HTML library.

Comparison Study

We will do a side by side comparison of both frameworks:

1.  Binding of the model with a root HTML element

AngularJS

First, let's take a look at the JavaScript where we first define a module, and then a controller.

  1. var app = new angular.module("mainApp", []);
  2. app.controller("DocController", function ($q, $scope) {
  3. ...
  4. SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function () { $scope.init() });
  5. }

In the HTML, the module and controller are bound by using the directives (ng-app & ng-controller):

  1. <div ng-app=mainApp>
  2.    <div class="RecentlyAdded Recommended" ng-controller="DocController">
  3.    </div>
  4. </div>

Description

Modules and controllers play the major role in binding data. A controller contains the definition of the models; in other words, the attributes and the functions. An HTML page can have multiple modules and controllers; each HTML section is bound to a particular module/controller by using ng-app & ng-controller directives with the respective names.

In the controller's main body, $scope.init() is used to initialize the data, as in the data from the SharePoint library.

Knockout

First, we will examine the JavaScript to create a view model and then bind the view model to the HTML element by using ko.applyBindings method.

  1. var listviewin = new MainModal();
  2. listviewin.init();
  3. ko.applyBindings(listviewin, document.getElementById('RecentDocument'));

The HTML is as follows and there are no tags or directives at this level:

<div class="RecentDocument">
</div>

Description

In Knockout, first a model object, "listviewin", is initialized; then, the method init() is called to populate the data. The model is bound to the HTML element by using ko.applyBindings where we pass the view model and HTML element as a parameter.

Summary

In the Angular ng-app, ng-controller is used to bind the data to a section.

In Knockout, ko.applyBindings is used to bind the data with the model and the HTML element as an object.

Now, we have seen how the initialization and binding of the root elements happen in both frameworks. The code to retrieve the data from SharePoint is the same for both. In the next section, we will see how the model is created and populated.

2.  Populating the model with data

AngularJS

Model elements and functions are defined in the controller. The important object is $scope; it serves as the glue between the controller and the view. In this example, the controller contains an array and a few functions.

  1. app.controller("DocController", function ($q, $scope) {
  2. $scope.RecentDocs = [];
  3. $scope.OnLike = function (rdoc) {
  4. ...
  5. };
  6. $scope.init = function () {   // init
  7. ...
  8.    docs.push({
  9.       Id: oListItem.get_id(),
  10.       ListId: ListId = oList.get_id(),
  11.       Title: oListItem.get_item('Title'),
  12.       ShortDesc: oListItem.get_item('ShortDesc'),
  13.       Category: oListItem.get_item('Category'),
  14.       LikedBy: likedBy,
  15.       LikedCount: likeCount,
  16.       LikeDisplay: likeDisplay,
  17.    });
  18.    $scope.RecentDocs = [];
  19.    $scope.RecentDocs = docs;
  20.    scope.$apply();
  21. },
  22. function (sender, args)
  23. {
  24.    alert(args.get_message());
  25. });
  26. };
  27. RegisterSod("reputation.js", _spPageContextInfo.webAbsoluteUrl +
       "/_layouts/15/reputation.js");
  28. RegisterSod("angular.js", _spPageContextInfo.webAbsoluteUrl +
       "/SiteAssets/JavaScript/angular.min.js");
  29. RegisterSodDep("reputation.js", "sp.js");
  30. SP.SOD.executeFunc('sp.js', 'SP.ClientContext',
       function () { $scope.init() });
  31. });

Description

The array is populated by passing a JSON object and then using $scope.apply() that re-applies the data to the UI. All the elements defined are prefixed with $scope.

Knockout

Here, we define two models: one for the main object and the other for the document object.

    1. var DocViewModel = function(model)
    2. {
    3.    var self = this;
    4.    self.Id = ko.observable(model.Id);
    5.    self.ListId = ko.observable(model.ListId);
    6.    self.Title = ko.observable(model.Title);
    7.    self.ShortDesc = ko.observable(model.ShortDesc);
    8.    self.Category = ko.observable(model.Category);
    9.    self.LikedBy = ko.observable(model.LikedBy);
    10.    self.LikedCount = ko.observable(model.LikedCount);
    11.    self.LikeDisplay = ko.observable(model.LikeDisplay);
    12.    self.OnLike = function () {
    13. }

 

  1. var MainModal = function () {
  2.    var self = this;
  3.    self.RecentDocs = ko.observableArray();
  4.    self.IsAdmin = ko.observable();
  5.    self.init = function () {   // init
  6.    ...
  7.    self.RecentDocs.push(new DocViewModel({
  8.       Id: oListItem.get_id(),
  9.       ListId: ListId = oList.get_id(),
  10.       Title: oListItem.get_item('Title'),
  11.       ShortDesc: oListItem.get_item('ShortDesc'),
  12.       Category: oListItem.get_item('Category'),
  13.       LikedBy: likedBy,
  14.       LikedCount: likeCount,
  15.       LikeDisplay: likeDisplay
  16.    });
  17. }

Description

There are two models in this implementation. One is the mainModel (entry point) and the other is the document model (document properties). In the main model, data is retrieved by using SharePoint's client object model, looping through the data to form the collection. Also, the OnLike method is defined at the document model level.

All the properties are defined by using ko.observable() or ko.observableArray(). Observable properties allow two-way binding and direct assignment to a variable without the observable making the binding one.

Summary

In Angular, all the attributes defined have two-way binding by default and in Knockout the "ko.observable" key makes the attributes two-way. In Angular, if the attribute is not defined but used in the HTML, it doesn't throw any error whereas Knockout will give a binding error.

We have seen how root elements are bounded and data populated in the models. Now, we will see how the models in the JS are applied to the HTML UI.

3.  Binding of data with UI elements

AngularJS

In the HTML, we specify the binding by using {{ }} braces or ng-directive:

  1. <div class="RAItem" ng-repeat>="rdoc in RecentDocs">
  2.    <div class="RADetails">
  3.       <h3><a href="#">{{rdoc.Title}} </a></h3>
  4.       <p><p ng-model="rdoc.ShortDesc"></p>
  5.          {{Category}}
  6.       </p>
  7.    </div>
  8.    <ul>
  9.    <li>
  10.       <a href ng-click="OnLike(rdoc)">{{rdoc.LikeDisplay}}
           ({{rdoc.LikedCount}})</a>
  11.    </li>
  12.    <li><a href="#" ng-show="IsAdmin">Share</a></li>
  13.    <li><a href="#" ng-hide="IsAdmin">Save</a></li>
  14.    </ul>
  15. </div>

Description

In the preceding example, the ng-repeat directive is used to loop through the items in the collection and ng-model; also, {{<attribute name>}} is used to bind the attribute to HTML elements.

Ng-click is used to bind a click event to the HTML element that can be compared to the "OnClick" attribute in HTML. Ng-show and ng-hide display or hide the element based on the "IsAdmin" value.

Knockout

<div class="RecentlyAdded" id="RecentDocument"data-bind ="foreach:Documents">
   <div class="RAItem">
      <img class="RAImg" src="../HeroConfigurations/16.png"
           data-bind>="attr:{src:DocIcon}">
      <div class="RADetails">
         <h3><a href="#" target="_blank"data-bind="text:Title,click:TitleClick"></a>
         <p data-bind="html: Description"></p>
         <ul>
            <li><a data-bind="text:LikeText,click:setLike" href="#" ></a></li>
            <li><a data-bind="click:OpenShareDialog, visible:
                IsAdmin" href="#" >Share</a></li>
            <li><a data-bind="click:Save, visible: IsAdmin "  href="#" >Save</a></li>
         </ul>
      </div>
   </div>


</div>

Description

In the previous example, "data-bind" with foreach is used to loop through the items in the collection, to bind the attributes to HTML elements, and also to attach a click event and to hide the elements.

Multiple behaviors are defined by separating them by commas ",".

For example: data-bind="text:LikeText,click:setLike".

Summary

In AngularJS, the binding to the HTML elements is done by using directives and {{}}. Angular also gives a provision to define custom directives as elements or attributes.

In Knockout, binding data to the HTML is done only by using "the data-bind" attribute on HTML elements with different parameters such as "text","visible", "click"; these are a few that we saw in the preceding examples. Knockout also allows the creation of custom bindings by using "ko.bindingHandlers".

Summary

From a SharePoint perspective, both the frameworks allow quick development of custom UIs. In Knockout, all the binding is using the "data-bind" attribute with different parameters including for custom bindings names, this giving a standard way of binding data. In Angular, each item has its own directive and it also allows creation on a custom directive at the element or attribute level. Choosing the framework is a developer's choice based on their ease to develop; they can choose either of them. The advantage with Angular is the support for unit testing because the Angular injector subsystem is responsible for creating components and resolving dependencies.

References



Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date