MVC using Angular and Promises in SharePoint 2013

Lately I’ve been reading all kinds of cool articles about Angular JS. One of them written by Jeremy Thake: http://www.jeremythake.com/2013/10/sharepoint-hosted-app-with-angularjs-for-mvc-javascript/. This was an excellent starting point for me to see what Angular has to offer. I noticed two things:

  1. Scoping in Angular JS is pretty tricky. Jeremy’s example was written in the global namespace and I wanted to add some best practices.
  2. Combine the scoping difficulty with multiple Asynchronous calls from your JavaScript code and it’s easy to get lost. Promises to the Rescue!

Namespaces in JavaScript

A very common issue in coding JavaScript is conflicts between 2 variables with the same name in the global namespace. If you code in multiple JS files it’s possible that var i for instance is used more then once. This can easily be avoided by using Namespaces like this:

var AP = window.AP || {};

This statement loads AP from the global namespace if it already exists and otherwise creates a new namespace AP. The functions or vars created in that namespace will never conflict with same function- or varnames in a different namespace. #winning!

AP.WebPropQuery = function() { var i = 0; };
AP.ContactsQuery = function() { var i = 1; };

This makes the code maintainable and readable while the var i now is always the right value in the right context. If you want to know more this is an excellent read:

http://www.kenneth-truyers.net/2013/04/27/javascript-namespaces-and-modules/

Angular JS

My goal here was to use the namespacing practice with the Angular is written. Angular also has a way of scoping everything and I found challenging to combine it with this approach of Namespaces.

With a directive in the HTML code “ng-app” we specify the scope of this Angular App. Using this code I register it as a Module so I also can use a Service later on.

AP.Promise = angular.module('AP.Promise', []);

My goal in this example is to load a Property from the Web’s Propertybag. This property contains the name of a Contacts list. I want all the contacts to be rendered with Full Name and Email address.

Controllers

A controller in the MVC pattern handles a certain part of the View. In our case a list of Contacts. I used the directive ng-controller=”ContactController” and matching JS code

/*
* Angular Controller Registration
* Dependencies: $scope and the $ProfileService
*/
AP.Promise.controller('ContactController', ['$scope', '$ContactService', function ($scope, $ContactService) {
   $scope.contacts = [];
   $ContactService.getContacts($scope);
}]);

When adding this Controller to the Module, I pass along 2 dependencies. $scope (which is kind of mandatory otherwise the controller would be useless) and $ContactService. In this service the logic is stored for getting the data out of SharePoint. The contents of the service

/*
* Angular Service Registration to Support SharePoint interaction
*/
AP.Promise.service('$ContactService', function () {
//Service Method to Get the Contacts
  this.getContacts = function ($scope) {
     //Logic
  };
});

This is the base of our Angular code. Now we need some JS to get the data out of SharePoint. In this example I’m using the REST API.

REST calls and Promises

One library that makes our lives easier when using REST API is jQuery. You might have noticed that the $ char is very popular and is used by lots of JavaScript libraries. We have to be carefull not to call into the wrong library here.

At the start of this post I created two functions in the AP namespace which we are going to fill. One for the Call to get the Web Property and one for the Call to get the Contacts.

/*
* Query object to retrieve Property from Propertybag
* Return: Promise object that is filled with data after ASync call is returned
*/
AP.WebPropQuery = function ($) {
   var deferred = $.Deferred();
   var execute = function () {
      $.ajax({
         url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/AllProperties?$select=p42contacts",
         method: "GET",
         headers: { "accept": "application/json;odata=verbose" },
         success: function (data) {
            deferred.resolve(data);
         },
         error: function (err) {
            deferred.reject(err);
         }
      });
      return deferred;
   };
   return {
      execute: execute
   }
}(jQuery);

This is what the first function looks like. Now the important part is the Promise var deferred = $.Deferred(); This var is called a promise and is kind of like an object placeholder which you can work with untill the Async call replaces the object with the final result of the call. Using these promises you don’t have to nest the second call in the success function of the $.ajax call. This increase readability and maintainability of your code. Scott Hillier wrote a great piece on Promises and how to use them:
https://curah.microsoft.com/11711/using-promises-patterns-in-sharepoint-2013-apps-with-javascript-and-jquery

Making the function calls

The final missing piece in this is making the function calls from the logic in the Angular Service. Again the code of the service with the missing logic

/*
* Angular Service Registration to Support SharePoint interaction
*/
AP.Promise.service('$ContactService', function () {
   //Service Method to Get the Contacts
   this.getContacts = function ($scope) {
      //First Async Call
      AP.WebPropQuery.execute().promise().then(function (data) {
         //Second Async Call depending on data from 1st call
         AP.ContactsQuery.execute(data.d.p42contacts).promise().then(function (data) {
            //Data to work with
            var results = data.d.results;
            for (var i = 0; i < results.length; i++) {
               $scope.contacts.push({ fullname: results[i].FullName, email: results[i].Email });
            }
            //Apply scope to see results
            $scope.$apply();
         }, function (err) { });
      }, function (err) { });
   };
});

AP.WebPropQuery.execute() is the regular function call. With .promise().then() it now is possible to add a second function call based on the promise that is returned in the first. That second call also returns a promise and with that data we can populate our Model.

The View

The View to match all this JS code is a very plain and simpel piece of HTML.

<script type="text/javascript" src="/Style Library/Scripts/p42.contacts.js"></script>
<div ng-app="AP.Promise">
   <div ng-controller="ContactController">
      <ul>
         <li ng-repeat="contact in contacts">{{contact.fullname}} - {{contact.email}}</li>
      </ul>
   </div>
</div>

Results

The thing I really like about this combination of Promises and Angular is that your JavaScript has very clear build up. It’s very readable and maintainable and on top of that it’s very fast! This way we don’t have to wait on all the SharePoint JS files to be loaded as well since we use the REST API. Very powerfull stuff!

You can download a Visual Studio 2012 Solution here. This solution contains a Sandboxed Solution (still fine for provisioning stuff!) with a default Script Editor WebPart that holds the View HTML with the Angular directives.

Todo yourself:

– Create a Contact List and make sure the Full Name and Email are filled
– Create a Property using SP Designer called “p42contacts” in the rootweb and add the name of the Contact List as value.

One comment

  1. Alshah says:

    Excellent Article… to the point and explains very well. Keep you the good work!

Leave a Reply

Your email address will not be published. Required fields are marked *