Experimenting with AngularJS in SharePoint Part 2

If you haven't checked out Part 1, please have a look. I've made some changes since then. I'll be going over the changes here in Part 2.

First of all, I moved all of the AngularJS application code into its own javascript file. My ascx page is now simplified to look something like this:

[code lang="html"]
<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="VisualWebPart1.ascx.cs" Inherits="MyFirstVisualWebPart.VisualWebPart1.VisualWebPart1" %>
<script src="/_layouts/15/MyFirstVisualWebPart/angular.min.js"></script>
<script>
var siteUrl = "<%= SPContext.Current.Web.Url %>";
</script>
<script src="/_layouts/15/MyFirstVisualWebPart/myngapp.js"></script>

<h1>My First Visual WebPart featuring Angular.js</h1>

<div ng-app="myApp">
<div ng-controller="QuestionnaireCtrl">
<span>{{hello}}</span>
<ol>
<li ng-repeat="question in questions">
<span>{{question.text}}</span>
<br />
<input type="text" ng-model="question.answer"/>
</li>
</ol>
<input type="submit" value="Submit" ng-click="addAnswers($event)"/>
</div>
</div>
[/code]

I deployed the AngularJS library and myngapp.js into SharePoint's layouts folder. Now I can make code changes to my Angular app without redeploying the web part. I declared my ng-app directive with a name of 'myApp' because I wanted to define a service (more details on this later).

myngapp.js

myngapp.js is where the bulk of my changes were made. I cleaned up my controller a bit, and created a service that will perform the AJAX and JSOM calls into SharePoint.

Here is the code for my controller:

[code lang="javascript"]

function QuestionnaireCtrl($scope, $SharePointJSOMService) {

var promise = $SharePointJSOMService.getQuestions($scope, 'FoodQuestions');
promise.then(function (data, status, headers, config) {
$scope.questions = [];
angular.forEach(data.data.d.results, function (question) {
$scope.questions.push({
text: question.Question,
answer: ""
});
});
}, function (data, status, headers, config) {
console.log("Error " + status);
});

$scope.addAnswers = function ($event) {
$event.preventDefault();

var promise = $SharePointJSOMService.addAnswers($scope, 'FoodAnswers');

promise.then(function (message) {
alert(message);
}, function (reason) {
alert(reason);
});
};
}
[/code]

  • I am now using angular.forEach instead of underscore.js's each function to iterate through my data result. Which resulted in me no longer requiring the use of underscore.js.
  • I added functionality to retrieve questions from a SharePoint list called FoodQuestions, and storing the answers in FoodAnswers. The questions are no longer hard coded.
  • I am dependency injecting my custom service called $SharePointJSOMService into my controller. This service returns a promise.

Here is the implementation of my custom service:

[code lang="javascript"]
var myApp = angular.module('myApp', []);

myApp.service('$SharePointJSOMService', function ($q, $http) {
// Store answers into SharePoint
this.addAnswers = function ($scope, title) {
var deferred = $q.defer();

var valid = true;
angular.forEach($scope.questions, function (que) {
if (valid) {
if (que.answer === "") {
valid = false;
}
}
});

if (!valid) {
deferred.reject('Please answer all the questions');
return deferred.promise;
}

var clientContext = new SP.ClientContext(siteUrl);
var web = clientContext.get_web();
var list = web.get_lists().getByTitle(title);

angular.forEach($scope.questions, function (question, i) {
// create the ListItemInformational object
var listItemInfo = new SP.ListItemCreationInformation();
// add the item to the list
var listItem = list.addItem(listItemInfo);
// Assign Values for fields
listItem.set_item('Question', question.text);
listItem.set_item('Answer', question.answer);
listItem.set_item('Title', 'Question ' + (++i));

listItem.update();
});

clientContext.executeQueryAsync(
Function.createDelegate(this, function () {
$scope.$apply(function () {
var questions = $scope.questions;
angular.forEach(questions, function (question) {
question.answer = "";
});
deferred.resolve('Thank you, have a nice day.');
});
}),
Function.createDelegate(this, function () {
scope.$apply(function (sender, args) {
deferred.reject('Request failed. ' + args.get_message() + 'n' + args.get_stackTrace());
});
})
);

return deferred.promise;
};

// Read from SharePoint List for Question
this.getQuestions = function ($scope, listName) {
var url = siteUrl + "/_api/web/lists/getByTitle('" + listName + "')/items?$select=Question";
return $http({
method: 'GET',
url: url,
headers: { "Accept": "application/json; odata=verbose" }
});
};
});
[/code]

  • My service contains 2 functions getQuestions, and addAnswers
  • My service depends on 2 other services ($q and $http)
  • addAnswers looks similar to what I was doing in Part 1, in addition to a simple validation check to make sure every question is answered
  • getQuestions uses the $http service to make a SharePoint 2013 REST API call to get data from the requested list

Conclusion

I believe I'm still scratching the surface as to what AngularJS is capable of. I find it nice that AngularJS includes a number of commonly used tools in one library. I did not need to include jQuery to perform AJAX or promises. I did not need to include Knockout.js for data binding. I did not need to include underscore.js for handy utility functions. I also found some Twitter Bootstrap components written for AngularJS if you need some fancy front end stuff. Unfortunately, you will need to customize which Bootstrap css components to include in order to make it play nice with SharePoint.

It’s Time To Transform

Let us show you how much easier your work life can be with Bonzai Intranet on your team.