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.
The following features will be implemented such View All Items and Delete Item.
Add New Item
Edit Item
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.
<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.
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
"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
"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.
"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.
<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:
"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.
Click ok, Enter your site URL which you want to use for debugging, choose SharePoint-Hosted and finally click on finish.
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.
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:
<%----%>
<%@ 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" %>
<%----%>
<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.
"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
.
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:
="1.0"="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.
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.