Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

SharePoint 2013 and Angularjs

4.60/5 (22 votes)
2 Jan 2016CPOL5 min read 179K   3.3K  
Taking the great advantage of two way data binding of AngularJS in SharePoint 2013. AngularJS can be used in Content Editor Web Part or SharePoint Apps.

Introduction

We all know that AngularJS is currently most famous for both way data binding framework among all. This article aims to show how this advantage (both way data binding) can be taken in SharePoint 2013. Before reading this article, you must have knowledge of AngularJS and SharePoint 2013 REST API.

AngularJS can help us in developing Content Editor Web Parts and apps. In most of the cases, I found that people choose Sandbox solution but the REST API of SharePoint 2013 is so powerful that Sandbox solutions can be replaced by the Content Editor Web Parts or apps easily. In this article, we will see how AngularJS can be used in Content Editor Web Parts and SharePoint hosted apps.

Initial Setup

For keeping everything simple, I will demonstrate a CRUD operation on a list because we know that CRUD is a very common part in any kinds of business requirement. I named the list as People. Apart from that, you will need SharePoint Designer 2013 and Visual Studio 2013 or 2012. Finally, make sure that your server is prepared for developing SharePoint hosted app.

people-list

The following features will be implemented such View All Items and Delete Item.

all

Add New Item

add

Edit Item

edit

AngularJs in Content Editor Web Part

If you are not familiar with Content Editor Web Part please have a look here. Now from Designer, create a folder in any asset library and then a text file. In my case, I have created a folder named People and text file named people-main.txt inside Site Assets library. Finally, add this file URL in Content Link. My URL becomes /SiteAssets/People/people-main.txt as I am using my root site collection. If you prefer to work from any sub-site, then add its path to the URL properly.

Now text file is ready for adding HTML code.

HTML
<link href="/SiteAssets/People/css/style.css" rel="stylesheet" type="text/css">
<script src="/SiteAssets/People/lib/angular.min.js" type="text/javascript"></script>
<script src="/SiteAssets/People/lib/angular-route.min.js" type="text/javascript"></script>
<script src="/SiteAssets/People/js/app.js" type="text/javascript"></script>
<script src="/SiteAssets/People/js/services/baseSvc.js" type="text/javascript"></script>
<script src="/SiteAssets/People/js/services/people/people.js" type="text/javascript"></script>
<script src="/SiteAssets/People/js/controllers/people/all.js" type="text/javascript"></script>
<script src="/SiteAssets/People/js/controllers/people/add.js" type="text/javascript"></script>
<script src="/SiteAssets/People/js/controllers/people/edit.js" type="text/javascript"></script>

<div data-ng-app="peopleApp">
    <div data-ng-view class="people-app"></div>
</div>

Actually, in this file, I have added the necessary script sources and initialised ng-app and ng-view. Rather than that, I have linked css file here also. 

folder-structure

Now let's have look at script files. Everything is very much similar as typical AngularJS apps. Now let me explain what are the differences.

app.js

JavaScript
"use strict";
(function() {
    angular.module("peopleApp", ["ngRoute"])
        .config(["$routeProvider", function($routeProvider) {
            $routeProvider.when("/", {
                templateUrl: "/SiteAssets/People/templates/people/all.html",
                controller: "allPeopleCtrl"
            }).when("/addPerson", {
                templateUrl: "/SiteAssets/People/templates/people/add.html",
                controller: "addPersonCtrl"
            }).when("/editPerson/:personId", {
                templateUrl: "/SiteAssets/People/templates/people/edit.html",
                controller: "editPersonCtrl"
            });
        }]);
})();

peopleApp is created here and necessary routes are defined.

baseSvc.js

JavaScript
"use strict";
(function() {
    angular.module("peopleApp")
        .factory("baseSvc", ["$http", "$q", function($http, $q) {
            var baseUrl = _spPageContextInfo.webAbsoluteUrl;
            var getRequest = function(query) {
                var deferred = $q.defer();
                $http({
                        url: baseUrl + query,
                        method: "GET",
                        headers: {
                            "accept": "application/json;odata=verbose",
                            "content-Type": "application/json;odata=verbose"
                        }
                    })
                    .success(function(result) {
                        deferred.resolve(result);
                    })
                    .error(function(result, status) {
                        deferred.reject(status);
                    });
                return deferred.promise;
            };
            var postRequest = function(data, url) {
                var deferred = $q.defer();
                $http({
                        url: baseUrl + url,
                        method: "POST",
                        headers: {
                            "accept": "application/json;odata=verbose",
                            "X-RequestDigest": document.getElementById("__REQUESTDIGEST").value,
                            "content-Type": "application/json;odata=verbose"
                        },
                        data: JSON.stringify(data)
                    })
                    .success(function(result) {
                        deferred.resolve(result);
                    })
                    .error(function(result, status) {
                        deferred.reject(status);
                    });
                return deferred.promise;
            };
            var updateRequest = function(data, url) {
                var deferred = $q.defer();
                $http({
                        url: baseUrl + url,
                        method: "PATCH",
                        headers: {
                            "accept": "application/json;odata=verbose",
                            "X-RequestDigest": document.getElementById("__REQUESTDIGEST").value,
                            "content-Type": "application/json;odata=verbose",
                            "X-Http-Method": "PATCH",
                            "If-Match": "*"
                        },
                        data: JSON.stringify(data)
                    })
                    .success(function(result) {
                        deferred.resolve(result);
                    })
                    .error(function(result, status) {
                        deferred.reject(status);
                    });
                return deferred.promise;
            };
            var deleteRequest = function(url) {
                var deferred = $q.defer();
                $http({
                        url: baseUrl + url,
                        method: "DELETE",
                        headers: {
                            "accept": "application/json;odata=verbose",
                            "X-RequestDigest": document.getElementById("__REQUESTDIGEST").value,
                            "IF-MATCH": "*"
                        }
                    })
                    .success(function(result) {
                        deferred.resolve(result);
                    })
                    .error(function(result, status) {
                        deferred.reject(status);
                    });
                return deferred.promise;
            };
            return {
                getRequest: getRequest,
                postRequest: postRequest,
                updateRequest: updateRequest,
                deleteRequest: deleteRequest
            };
        }]);
})();

I make a generic service for all kinds of requests like GET, POST, UPDATE and DELETE so that we can inject it from anywhere. I am not describing here how these methods work because I have an another article about it. In that article, I demonstrated it using jQuery and in this article I have converted it into AngularJS. This baseSvc is injected in peopleService what benefitted us from repeating codes while getting, adding, updating and deleting items. Before sending any request, you have to pass required URL and parameters to the baseSvc methods.

JavaScript
"use strict";
(function() {
    angular.module("peopleApp")
        .factory("peopleService", ["baseSvc", function(baseService) {
            var listEndPoint = '/_api/web/lists';
            var getAll = function() {
                var query = listEndPoint + "/GetByTitle('People')/Items?$select=ID,
                         FirstName,LastName,Address";
                return baseService.getRequest(query);
            };
            var addNew = function(person) {
                var data = {
                    __metadata: {
                        'type': 'SP.Data.PeopleListItem'
                    },
                    FirstName: person.firstName,
                    LastName: person.lastName,
                    Address: person.address
                };
                var url = listEndPoint + "/GetByTitle('People')/Items";
                return baseService.postRequest(data, url);
            };
            var getById = function(personId) {
                var query = listEndPoint + "/GetByTitle('People')/GetItemById
                (" + personId + ")?$select=ID,FirstName,LastName,Address";
                return baseService.getRequest(query);
            };
            var update = function(person) {
                var data = {
                    __metadata: {
                        'type': 'SP.Data.PeopleListItem'
                    },
                    FirstName: person.firstName,
                    LastName: person.lastName,
                    Address: person.address
                };
                var url = listEndPoint + "/GetByTitle('People')/
                GetItemById(" + person.personId + ")";
                return baseService.updateRequest(data, url);
            };
            var remove = function(personId) {
                var url = listEndPoint + "/GetByTitle('People')/
                GetItemById(" + personId + ")";
                return baseService.deleteRequest(url);
            };
            return {
                getAll: getAll,
                addNew: addNew,
                getById: getById,
                update: update,
                remove: remove
            };
        }]);
})();

You may have a question that how did I construct the URLs and parameters for each request. Again, you have to check this article.

Now have look on templates and controllers.

All Items

This template is intended to show items and remove items.

HTML
<a href="#/addPerson" class="add-new-button">Add New Person</a>
<div class="all-people">
    <table>
        <tbody>
            <tr>
                <th>Fist Name</th>
                <th>Last Name</th>
                <th>Address</th>
                <th>Action</th>
            </tr>
            <tr data-ng-repeat="person in people">
                <td>{{person.FirstName}}</td>
                <td>{{person.LastName}}</td>
                <td>{{person.Address}}</td>
                <td>
                    <a href="#/editPerson/{{person.ID}}">
                       <img src="/SiteAssets/People/images/edit.png" alt=""></a>
                    <a href="" data-ng-click="removePerson(person)">
                       <img src="/SiteAssets/People/images/delete.png" alt=""></a>
                </td>
            </tr>
        </tbody>
    </table>
</div>

Respective controller for this view looks like the following:

JavaScript
"use strict";
(function () {
    angular.module("peopleApp")
        .controller("allPeopleCtrl", ["$scope", "peopleService",
        function ($scope, peopleService) {
            peopleService.getAll()
                .then(function (response) {
                $scope.people = response.d.results;
            });
            $scope.removePerson = function(person){
                peopleService.remove(person.ID)
                .then(function(response){
                    var personIndex = $scope.people.indexOf(person);
                    $scope.people.splice(personIndex,1);
                });
            };
        }]);
})();

You must notice that I have requested ID, FirstName, LastName and Address in peopleService using getAll(). In this controller, items returned by peopleService are binded to the view using ng-repeat. Removing item is also implemented here. I am not describing other views and controllers here because it will elaborate this article unnecessarily. Hope you will understand everything after downloading my source code.

NB: Don't use any form tag inside the template.

AngularJS in Hosted App

Before developing hosted app for on-premises, environment must be configured properly. In that case, office-365 is much more easier. Environment configuration is not needed at all. Now, create a developer site collection by choosing Developer Site template. We cannot use any other sites except Developer Site for app development. Now add the same People list in this site.

Create a new project in Visual Studio and choose App for SharePoint 2013 from Office/SharePoint template.

New Project

Click ok, Enter your site URL which you want to use for debugging, choose SharePoint-Hosted and finally click on finish.

Url selection

When we create a SharePoint Hosted app, it gives us a standard folder structure for our scripts and design files. We cannot follow what we did in Content Editor. I have added my files in the following structure.

app-folder

Basically, I have added my all scripts in Scripts folder, styles in Content folder and images to the Images folder. For template files, I have added a new folder named Templates. Now, open the Default.aspx page and modify it like following:

ASP.NET
<%-- The following 4 lines are ASP.NET directives needed when using SharePoint components --%>
<%@ Page Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, 
Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" 
MasterPageFile="~masterurl/default.master" Language="C#" %>
<%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" 
Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" 
Assembly="Microsoft.SharePoint, 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" %>
<%-- The markup and script in the following Content element 
will be placed in the <head> of the page --%>
<asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
    <script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
    <script type="text/javascript" src="/_layouts/15/sp.js"></script>
    <meta name="WebPartPageExpansion" content="full" />
    <link rel="Stylesheet" type="text/css" href="../Content/style.css" />
    <script src="../Scripts/angular.min.js" type="text/javascript"></script>
    <script src="../Scripts/angular-route.min.js" type="text/javascript"></script>
    <script src="../Scripts/peopleApp/app.js" type="text/javascript"></script>
    <script src="../Scripts/peopleApp/services/baseSvc.js" type="text/javascript"></script>
    <script src="../Scripts/peopleApp/services/people/people.js" type="text/javascript"></script>
    <script src="../Scripts/peopleApp/controllers/people/all.js" type="text/javascript"></script>
    <script src="../Scripts/peopleApp/controllers/people/add.js" type="text/javascript"></script>
    <script src="../Scripts/peopleApp/controllers/people/edit.js" type="text/javascript"></script>
</asp:Content>
<asp:Content ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">
    SharePoint 2013 Hosted App and AngularJS Demo
</asp:Content>
<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
    <div data-ng-app="peopleApp">
        <div data-ng-view class="people-app"></div>
    </div>
</asp:Content>

In this page, I have added all script and style references and also initialized peopleApp here. Now there is a little change in app.js and baseSvc.js file. In app.js, we have to change the template URLs as following.

JavaScript
"use strict";
(function () {
    angular.module("peopleApp", ["ngRoute"])
        .config(["$routeProvider", function ($routeProvider) {
            $routeProvider.when("/", {
                templateUrl: "../Templates/people/all.html",
                controller: "allPeopleCtrl"
            }).when("/addPerson", {
                templateUrl: "../Templates/people/add.html",
                controller: "addPersonCtrl"
            }).when("/editPerson/:personId", {
                templateUrl: "../Templates/people/edit.html",
                controller: "editPersonCtrl"
            });
        }]);
})();

In the baseSvc.js, we have to use _spPageContextInfo.siteAbsoluteUrl instead of _spPageContextInfo.webAbsoluteUrl for baseUrl.

JavaScript
var baseUrl = _spPageContextInfo.siteAbsoluteUrl;

We are all done in developing SharePoint Hosted app using AngularJS. Another thing, I must mention that whenever you add any file into your project, please check the respective Elements.xml file. Scripts, Content and Images have their own Elements.xml file. For example, I have added template files inside Templates folder, So my main Elements.xml file should look like the following:

XML
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="RootModule">
    <File Path="Templates\people\add.html" 
    Url="Templates/people/add.html" ReplaceContent="TRUE" />
    <File Path="Templates\people\all.html" 
    Url="Templates/people/all.html" ReplaceContent="TRUE" />
    <File Path="Templates\people\edit.html" 
    Url="Templates/people/edit.html" ReplaceContent="TRUE" />
  </Module>
</Elements>

Now right click on your project and click on deploy. I hope you will find following view in browser.

app home

Conclusion

Hope you enjoyed this article. Now download my source code and start digging into it in your own way. Use comment section for issues and suggestions.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)