Subscribe via RSS Feed Connect with me on LinkedIn

Experimenting with AngularJS in SharePoint Part 2

[ 4 ] September 5, 2013 |

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:

<%@ 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>

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:


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);
        });
    };
}
  • 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:

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" }
        });
    };
});
  • 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.

Category: Blog, SharePoint 2013

About Jeff Yan: View author profile.

Comments (4)

Trackback URL | Comments RSS Feed

  1. Christo says:

    Great little two-part experiment. Makes for some interesting little apps inside of SharePoint. :)

  2. Raf says:

    Great article, Thnx !

  3. […] apart from Meligo’s post (which was a great starter to get the Todo app in my App) and Jeff Yan from Dynamic Owl. I really like what Jeff had done with the AngularJS Services to separate out the […]

  4. Jens says:

    Great post!

Leave a Reply




If you want a picture to show with your comment, go get a Gravatar.