Category Archives: AngularJS

Using the SharePoint Client Object Model in AngularJS Apps

A common problem for SharePoint developers new to AngularJS is using the client object model within the context of AngularJS scopes.

The Problem Explained…

If you already understand AngularJS, feel free to skip this part!

Consider a controller that displays the name of the current user.

<div ng-controller="IQAppPart">
  <div ng-if="!!UserName">
     <h1>Hello <span ng-bind="UserName"></span></h1>
  </div>
 </div>
 

If the $scope variable of the IQAppPart has a value for UserName the greeting appears.

The IQAppPart controller function looks like this:

var iqAppPartModule = angular.module(‘iqAppPartModule’, []);
var iqAppPartController = iqAppPartController || {};
iqAppPartController.IQAppPart = function ($scope) {
//Make a request for the user
var ctx = SP.ClientContext.get_current();
var user = ctx.get_web().get_currentUser();
ctx.load(user);

//Send the request to SharePoint
ctx.executeQueryAsync(function () {
    //Set the UserName
    $scope.UserName = user.get_title();
  }, function (error) {
    alert(error.get_message());
  });
};
iqAppPartModule.controller(‘IQAppPart’, [‘$scope’, iqAppPartController.IQAppPart]);

Notice the highlighted line. This sets UserName and you might expect that this would in turn cause AngularJS’s magic data binding to display the greeting, but instead nothing would happen.

The code will run without error and retrieve the UserName, but that’s all. The greeting will not display. The reason for this is hard to see if you are new to AngularJS because the code sets the $scope and the line that does it is inside the controller function!

To understand the problem you must first realize that the call to SharePoint, executeQueryAsync, calls the success handler when it receives the result from SharePoint. The function call is an asynchronous operation that AngularJS knows nothing about.

The second thing you need to understand is that Angular’s model binding expects all operations of interest to start within the confines of an Angular process. When a page that is using Angular starts an Angular app via the presence of the ng-app directive on an HTML element or by calling angular.bootstrap(element), Angular starts listening for events specified element (with the attribute or passed to the bootstrap function) and all of the element’s descendants. So, it knows about clicks and other events. Whenever one happens, it reevaluates the model and then updates the HTML view.

Adding a call to $apply() as shown below fixes the problem. The call to $scope.$apply() tells Angular that something has changed and that it should do its work.

var iqAppPartModule = angular.module('iqAppPartModule', []);
var iqAppPartController = iqAppPartController || {};
iqAppPartController.IQAppPart = function ($scope) {
 //Make a request for the user
 var ctx = SP.ClientContext.get_current();
 var user = ctx.get_web().get_currentUser();
 ctx.load(user);
 //Send the request to SharePoint
 ctx.executeQueryAsync(function () {
   //Set the UserName
   $scope.UserName = user.get_title();
   $scope.$apply();
  }, function (error) {
   alert(error.get_message());
  });
};
iqAppPartModule.controller('IQAppPart', ['$scope', iqAppPartController.IQAppPart]);
 

Calling $apply() in the context of a controller is a big sign that you are doing it wrong!

Instead we need a mechanism that makes ClientContext.executeQueryAsync() work like any other service in AngularJS so that Angular will know when it is time to reevaluate the models and update the views.

The Solution

The solution is a service factory that wraps executeQueryAsync using Angular’s $q
service. $q is a service that helps you run functions asynchronously, and use their return values (or exceptions) when they are done processing.

//Angular service Wrapper for ClientContext executeQuery.
//The $q service makes it easy to wrap SharePoint's context.executeQueryAsync for use with Angular
var module = angular.module('iqPart', [])
 .factory('SharePointService', ['$q', function ($q) {
   var SharePointService = {};
   SharePointService.executeQuery = function (context) {
     var deferred = $q.defer();
     context.executeQueryAsync(deferred.resolve, function (o, args) {
       deferred.reject(args);
     });
     return deferred.promise;
  };
  return SharePointService;
}]);

To use the SharePointService we inject it into the controller and use it instead of ctx.executeQueryAsync as follows.

var iqAppPartModule = angular.module('iqAppPartModule', []);
var iqAppPartController = iqAppPartController || {};

iqAppPartController.IQAppPart = function ($scope, sharePointService) {
  //Make a request for the user
  var ctx = SP.ClientContext.get_current();
  var user = ctx.get_web().get_currentUser();
  ctx.load(user);
 sharePointService.executeQuery(ctx)
   .then(function () {
     $scope.UserName = user.get_title();
   },
   function (error) {
     alert(error.get_message());
 });
};
iqAppPartModule.controller('IQAppPart', ['$scope', 'SharePointService', iqAppPartController.IQAppPart]);
 

The end result is something like:

–Doug Ware