Sunday, March 29, 2015

SharePoint 2013 List CRUD operation using Angular and REST API

This article explains how to perform the CRUD operation on a SharePoint List using Angular JavaScript and the REST API.

Create an empty SharePoint 2013 solution, In that add the module of Pages, JS & CSS.

In pages module add new item, Test.aspx.
In JS module, add new Javascript file with name Controller.js , CommonServices.js & SharePointListModule.js
Other Javascripts needs to add:
1. angular.min.js
2. anugular.min.js.map

Project structure will look something like below:





In SharePointListModule.js will create a App of angular, Paste the below code.

 //App creation  
 var app = angular.module("SharePointAngApp", [])  


For doing a CRUD operation with SharePoint List, will create an Angular Service to make a SharePoint REST API calls,



 app.service('testService', ['$http', '$q', function ($http, $q) {//common services}]);

This testService having dependency of $http & $q
Information related to $http & $q  https://docs.angularjs.org/api/ng/service/$http , https://docs.angularjs.org/api/ng/service/$q

Following code showing how to get, add, delete & update tasks from specified SharePoint list,

 //Common service for SharePoint CRUD operations  
 app.service('testService', ['$http', '$q', function ($http, $q) {  
   var formDigest = $('#__REQUESTDIGEST').val();  
   //Define the http headers  
   $http.defaults.headers.common.Accept = "application/json;odata=verbose";  
   $http.defaults.headers.post['Content-Type'] = 'application/json;odata=verbose';  
   $http.defaults.headers.post['X-Requested-With'] = 'XMLHttpRequest';  
   $http.defaults.headers.post['If-Match'] = "*";  
   $http.defaults.headers.post['X-RequestDigest'] = formDigest;  
   //Get the tasks  
   this.getTasks = function (listTitle) {  
     var dfd = $q.defer();  
     $http.defaults.headers.post['X-HTTP-Method'] = ""  
     var query = "?$select=Title,Status,Priority,ID,Body";  
     var restUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/items" + query;  
     $http.get(restUrl).success(function (data) {  
       dfd.resolve(data.d.results);  
     }).error(function (data) {  
       dfd.reject("error getting tasks");  
     });  
     return dfd.promise;  
   }  
   //Create a task , for now static text, we can pass input fields to add apropriate value   
   this.addTask = function (listTitle) {  
     var dfd = $q.defer();  
     $http.defaults.headers.post['X-HTTP-Method'] = "";  
     var restUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/items";  
     $http.post(restUrl, {  
       __metadata: {  
         type: "SP.List"  
       },  
       Title: "New Tasks"  
     }).success(function (data) {  
       //resolve the new data  
       dfd.resolve(data.d);  
     }).error(function (data) {  
       dfd.reject("failed to add task");  
     });  
     return dfd.promise;  
   }  
   //Update a task  
   this.updateTask = function (listTitle, task) {  
     var dfd = $q.defer();  
     $http.defaults.headers.post['X-HTTP-Method'] = "MERGE";  
     var restUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/items(" + task.ID + ")";  
     $http.post(restUrl, {  
       __metadata: {  
         type: "SP.List"  
       },  
       Title: task.Title  
     }).success(function (data) {  
       //resolve something  
       dfd.resolve(true);  
     }).error(function (data) {  
       dfd.reject("error updating task");  
     });  
     return dfd.promise;  
   }  
   //Delete a task  
   this.deleteTask = function (listTitle, task) {  
     var dfd = $q.defer();  
     $http.defaults.headers.post['X-HTTP-Method'] = "DELETE";  
     var restUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/items(" + task.ID + ")";  
     $http.post(restUrl)  
       .success(function (data) {  
         //resolve something  
         dfd.resolve(true);  
       }).error(function (data) {  
         dfd.reject("error deleting task");  
       });  
     return dfd.promise;  
   }  
 }]);  

Now, its time to consume all these services by creating an angular controller.
 app.controller('spTasksController', function ($scope, $http, testService) {  
 //functions to consume the service methods});  

Full code to consume the above service methods is below, paste it in Controller.js
 //Controller to featch data from Tasks list  
 app.controller('spTasksController', function ($scope, $http, testService) {  
   $scope.editing = false;  
   //example populating tasks  
   testService.getTasks("Workflow Tasks").then(function (result) {  
     $scope.tasks = result;  
   });  
   //example to add task  
   $scope.addTask = function (listName) {  
     testService.addTask(listName).then(function (result) {  
       //update the scope with new task created  
       $scope.tasks.push(result);  
       $scope.task = {};  
       $scope.$apply();  
     });  
   };  
   // example to update task  
   $scope.updateTask = function (listName, item) {  
     testService.updateTask(listName, item).then(function (result) {  
       $scope.editing = $scope.tasks.indexOf(item);  
       $scope.$apply();  
     });  
   };  
   $scope.editItem = function (index) {  
     $scope.editing = $scope.tasks.indexOf(index);  
   }  
   $scope.saveField = function (index) {  
     if ($scope.editing !== false) {  
       $scope.editing = false;  
     }  
   };  
   // example to delete task  
   $scope.deleteTask = function (listName, item,index) {  
     testService.deleteTask(listName, item).then(function (result) {  
       $scope.tasks.splice(index, 1);  
       $scope.$apply();  
     });  
   };  
 });  

Now, its time to bind the view. add the below piece of code in test.aspx
 <%@ Page Language="C#" MasterPageFile="~masterurl/default.master" Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage,Microsoft.SharePoint,Version=15.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" meta:progid="SharePoint.WebPartPage.Document" meta:webpartpageexpansion="full" %>  
 <%@ 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" %>  
 <%@ Import Namespace="Microsoft.SharePoint" %>  
 <%@ Assembly Name="Microsoft.Web.CommandUI, 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" %>  
 <asp:Content ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">  
   <SharePoint:EncodedLiteral runat="server" Text="<%$Resources:wss,multipages_homelink_text%>" EncodeMethod="HtmlEncode" />  
   -   
      <SharePoint:ProjectProperty Property="Title" runat="server" />  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderPageImage" runat="server">  
   <img src="/_layouts/15/images/blank.gif?rev=23" width='1' height='1' alt="" />  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">  
   <label class="ms-hidden">  
     <SharePoint:ProjectProperty Property="Title" runat="server" />  
   </label>  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderTitleAreaClass" runat="server">  
   <SharePoint:UIVersionedContent runat="server" UIVersion="<=3">  
     <contenttemplate>  
           <style type="text/css">  
             td.ms-titleareaframe, .ms-pagetitleareaframe {  
               height: 10px;  
             }  
             div.ms-titleareaframe {  
               height: 100%;  
             }  
             .ms-pagetitleareaframe table {  
               background: none;  
               height: 10px;  
             }  
             .ms-viewheadertr ms-vhltr {  
               background: rgb(246,248,249); /* Old browsers */  
               /* IE9 SVG, needs conditional override of 'filter' to 'none' */  
               background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y2ZjhmOSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUwJSIgc3RvcC1jb2xvcj0iI2U1ZWJlZSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUxJSIgc3RvcC1jb2xvcj0iI2Q3ZGVlMyIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmNWY3ZjkiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);  
               background: -moz-linear-gradient(top, rgba(246,248,249,1) 0%, rgba(229,235,238,1) 50%, rgba(215,222,227,1) 51%, rgba(245,247,249,1) 100%); /* FF3.6+ */  
               background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(246,248,249,1)), color-stop(50%,rgba(229,235,238,1)), color-stop(51%,rgba(215,222,227,1)), color-stop(100%,rgba(245,247,249,1))); /* Chrome,Safari4+ */  
               background: -webkit-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* Chrome10+,Safari5.1+ */  
               background: -o-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* Opera 11.10+ */  
               background: -ms-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* IE10+ */  
               background: linear-gradient(to bottom, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* W3C */  
               filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f6f8f9', endColorstr='#f5f7f9',GradientType=0 ); /* IE6-8 */  
             }  
             .ms-vh2 {  
               font-size: 13px;  
               font-weight: 500;  
             }  
           </style> <!--[if gte IE 9]>  
  <style type="text/css">  
   .gradient {  
     filter: none;  
   }  
  </style> <![endif]-->  
     </contenttemplate>  
   </SharePoint:UIVersionedContent>  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">  
   <meta name="CollaborationServer" content="SharePoint Team Web Site" />  
   <SharePoint:StyleBlock runat="server">  
     .s4-nothome {  
      display:none;  
 }  
   </SharePoint:StyleBlock>  
   <script type="text/javascript" src="../JS/jquery.min.js">//<![CDATA[//]]></script>  
   <script type="text/javascript" src="../JS/angular.min.js"></script>  
   <script type="text/javascript" src="../JS/SharePointListModule.js"></script>  
   <script type="text/javascript" src="../JS/Controllers.js"></script>  
   <script type="text/javascript" src="../JS/Common.Services.js"></script>  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderSearchArea" runat="server">  
   <SharePoint:DelegateControl runat="server"  
     ControlId="SmallSearchInputBox" />  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderLeftActions" runat="server" />  
 <asp:Content ContentPlaceHolderID="PlaceHolderLeftNavBar" runat="server">  
   <div class="page">  
     <div class="row">  
       <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">  
         <div class="navHolder" id="fisCustomNav">  
           <ul class="nav-site" id="fisNavroot">  
           </ul>  
           <ul class="nav-device-main">  
             <li></li>  
           </ul>  
         </div>  
       </div>  
     </div>  
   </div>  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderPageDescription" runat="server">  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderBodyAreaClass" runat="server">  
   <SharePoint:StyleBlock runat="server">  
     .ms-bodyareaframe {  
      padding: 0px;  
 }  
   </SharePoint:StyleBlock>  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">  
   <form class="form-horizontal" name="taskForm" novalidate>  
     <div class="page" ng-app="SharePointAngApp">  
       <div class="row">  
         <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 replacement-btn-list">  
           <h3 class="text-bold text-color-dark ">List of Tasks:</h3>  
         </div>  
       </div>  
       <div class="row">  
         <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">  
           <div class="table-responsive replacement-list" ng-controller="spTasksController">  
             <table class="table">  
               <tr>  
                 <th>Tasks</th>  
                 <th>Status</th>  
                 <th>Prority</th>  
                 <th>Update</th>  
               </tr>  
               <tbody>  
                 <tr ng-repeat="task in tasks">  
                   <td><span ng-hide="editMode">{{task.Title}}</span><input type="text" ng-show="editMode" ng-model="task.Title" /></td>  
                   <td>  
                     <div>{{task.Status}}</div>  
                   </td>  
                   <td>  
                     <div>{{task.Priority}}</div>  
                   </td>  
                   <td><span>  
                     <input type="button" ng-hide="editMode" ng-click="editMode = true; editItem(task) " value="Edit" /></span>  
                     <span>  
                       <input type="button" ng-show="editMode" ng-click="editMode = false; updateTask('Workflow Tasks',task)" value="Save"></span></td>  
                   <td>  
                     <div>  
                       <input type="button" ng-click="deleteTask('Workflow Tasks',task,$index)" value="Delete" /></div>  
                   </td>  
                 </tr>  
                 <tr>  
                   <td>  
                     <input type="button" ng-click="addTask('Workflow Tasks')" value="Add" /></td>  
                 </tr>  
               </tbody>  
             </table>  
           </div>  
         </div>  
       </div>  
     </div>  
   </form>  
 </asp:Content>  


If you noticed in parent div i have added :  <div class="page" ng-app="SharePointAngApp">
and in child div added ng-controller

  <div class="table-responsive replacement-list" ng-controller="spTasksController">

Once we add the controller, we can access all the scope variables in child elements.

Output will looks like below:




Hope this will help you to build SharePoint applications using Angualr.
Happy learning!