We have to create Web based SharePoint Forms that can replace InfoPath Forms. So we chose Angular & REST to create our solution. I’ll not explain about Angular usage as it abundantly available. Just showing how we can integrate Angular with REST in SharePoint.
Solution Features:
- Create, Read, Update & Delete operations using Angular & REST
- One page solution for all operation
- Responsive design using bootstrap
- Confirmation message before deleting an item
- Save button disabled if required fields are empty
- Sorting on each column
- Single Search box to instantaneously search on multiple columns. (Title & Location)
The final outcome will look like this where you can view, add, update & delete list items. You can design as per your needs. I’m using Bootstrap framework to make responsive.

The list that holds the items:

I find adding ng-app in masterpage is easier to work with and don’t have create it again & again. There might be better way of doing, I’m new to Angular so let me if you have any suggestions.
Add reference to Angular.min in masterpage (make sure to add reference at top of masterpage instead at the end.), and add ng-app to div with id contentBox, this way everything will be under one ng-app.
<div id="contentBox" ng-app="CourseListApp">
Create a List and add your columns as required. Here I’m adding Location column to demonstrate. If you add your columns make sure to update the js accordingly.
Here is the js for the solution
var angCourseListApp = angular.module('CourseListApp', []); angCourseListApp.service('courseListService', ['$http', '$q', function ($http, $q) { //Get the courses this.getCourses = function (listTitle) { var dfd = $q.defer(); var fullUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/Items?$Select="; fullUrl += 'ID,Title,Location&$orderby=ID asc'; $.ajax({ url: fullUrl, type: "GET", headers: { "accept": "application/json;odata=verbose" }, success: function (data) { dfd.resolve(data.d.results); }, error: function (xhr) { dfd.reject('Error : (' + xhr.status + ') ' + xhr.statusText + ' Message: ' + xhr.responseJSON.error.message.value); } }); return dfd.promise; } //Create a course this.addCourse = function (listTitle, course) { var dfd = $q.defer(); var fullUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/items"; var itemType = "SP.Data." + listTitle + "ListItem"; var cat = { "__metadata": { "type": itemType }, "Title": course.title, "Location": course.location }; $.ajax({ url: fullUrl, type: "POST", contentType: "application/json;odata=verbose", data: JSON.stringify(cat), headers: { "Accept": "application/json;odata=verbose", // return data format "X-RequestDigest": $("#__REQUESTDIGEST").val() }, success: function (data) { //resolve the new data dfd.resolve(data.d); }, error: function (xhr) { dfd.reject('Error : (' + xhr.status + ') ' + xhr.statusText + ' Message: ' + xhr.responseJSON.error.message.value); } }); return dfd.promise; } //Update a course this.updateCourse = function (listTitle, course) { var dfd = $q.defer(); this.getItemById(listTitle, course.id).then( function (data1) { var itemType = "SP.Data." + listTitle + "ListItem"; var cat = { '__metadata': { "type": itemType }, 'Title': course.title, 'Location': course.location }; $.ajax({ url: data1.d.__metadata.uri, type: "POST", contentType: "application/json;odata=verbose", data: JSON.stringify(cat), headers: { "Accept": "application/json;odata=verbose", "X-RequestDigest": $("#__REQUESTDIGEST").val(), "X-HTTP-Method": "MERGE", "If-Match": data1.d.__metadata.etag }, success: function (data) { dfd.resolve(course); }, error: function (xhr) { dfd.reject('Error : (' + xhr.status + ') ' + xhr.statusText + ' Message: ' + xhr.responseJSON.error.message.value); } }); }); return dfd.promise; } this.getItemById = function (listTitle, itemID) { var dfd = $q.defer(); var fullUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/Items('" + itemID + "')"; $.ajax({ url: fullUrl, type: "GET", headers: { "accept": "application/json;odata=verbose" }, success: function (data) { dfd.resolve(data); }, error: function (xhr) { dfd.reject('Error : (' + xhr.status + ') ' + xhr.statusText + ' Message: ' + xhr.responseJSON.error.message.value); } }); return dfd.promise; } //Delete a course this.deleteCourse = function (listTitle, courseid) { var dfd = $q.defer(); this.getItemById(listTitle, courseid).then( function (data1) { $.ajax({ url: data1.d.__metadata.uri, type: "POST", headers: { "Accept": "application/json;odata=verbose", "X-RequestDigest": $("#__REQUESTDIGEST").val(), "X-HTTP-Method": "DELETE", "If-Match": data1.d.__metadata.etag }, success: function (data) { dfd.resolve(courseid); }, error: function (xhr) { dfd.reject('Error : (' + xhr.status + ') ' + xhr.statusText + ' Message: ' + xhr.responseJSON.error.message.value); } }); }); return dfd.promise; } }]); angCourseListApp.controller('CourseListController', function ($scope, courseListService) { courseListService.getCourses("Courses").then( function (result) { $scope.courses = []; angular.forEach(result, function (course) { var newCourse = { id: course.ID, title: course.Title, location: course.Location } $scope.courses.push(newCourse); }); $scope.orderByField = 'id'; $scope.reverseSort = false; }, function (reason) { $scope.errMessage = reason; }); $scope.edit = function (course) { $scope.newcourse = angular.copy(course); } $scope.update = function (newCourse) { if (!newCourse.id) { courseListService.addCourse("Courses", newCourse).then( function (course) { var courseAdded = { id: course.Id, title: course.Title, location: course.Location } $scope.courses.push(courseAdded); $scope.newcourse = ''; }, function (reason) { $scope.errMessage = reason; }); } else { courseListService.updateCourse("Courses", newCourse).then(function (c) { for (i in $scope.courses) { if ($scope.courses[i].id == c.id) { $scope.courses[i] = c; } } $scope.newcourse = ''; }, function (reason) { $scope.errMessage = reason; }); } } $scope.delete = function (courseId) { courseListService.deleteCourse("Courses", courseId).then(function (cid) { for (i in $scope.courses) { if ($scope.courses[i].id == cid) { $scope.courses.splice(i, 1); } } }, function (reason) { $scope.errMessage = reason; }); } $scope.clear = function () { $scope.newcourse = ''; } }); angCourseListApp.directive('confirmationNeeded', function () { return { priority: 1, terminal: true, link: function (scope, element, attr) { var msg = attr.confirmationNeeded || "Are you sure you want to continue?"; var clickAction = attr.ngClick; element.bind('click', function () { if (window.confirm(msg)) { scope.$eval(clickAction) } }); } }; });
Last step is to add a Script Editor web part and add the following html & reference to your courseList.js.
HTML for displaying the output.
<script src="/Style Library/Scripts/courseList.js" type="text/javascript"></script> <div ng-controller="CourseListController"> <div class="row"> <div class="col-lg-6"> <div class="well bs-component"> <ng-form class="form-horizontal" name="courseform" ng-submit="update(newcourse)"> <fieldset> <legend>Course</legend> <div class="form-group"> <div class="col-lg-12"> All fields are required </div> </div> <div class="form-group"> <label class="col-lg-2 control-label">ID</label> <div class="col-lg-10"> <input type="text" class="form-control input-sm" placeholder="ID" ng-model="newcourse.id" disabled/> </div> </div> <div class="form-group" > <label class="col-lg-2 control-label">Title</label> <div class="col-lg-10" ng-class="{ 'has-error' : courseform.iptitle.$invalid && !courseform.iptitle.$pristine }"> <input type="text" name="iptitle" class="form-control input-sm" placeholder="Title" ng-model="newcourse.title" required /> </div> </div> <div class="form-group" > <label class="col-lg-2 control-label">Location</label> <div class="col-lg-10" ng-class="{ 'has-error' : courseform.iplocation.$invalid && !courseform.iplocation.$pristine }"> <input type="text" name="iplocation" class="form-control input-sm" placeholder="Location" ng-model="newcourse.location" required /> </div> </div> <div class="form-group"> <div class="col-lg-10 col-lg-offset-2"> <a href="#" ng-click="clear()" class="btn btn-default btn-sm">Clear</a> <button ng-click="update(newcourse)" class="btn btn-success btn-sm" type="submit" value="Save" title="Save" ng-disabled="!courseform.$valid">Save</button> </div> </div> </fieldset> </ng-form> </div> </div> </div> <div class="row"> <div class="col-lg-6"> <input type="text" ng-model="query" placeholder="Search Courses" class="input-sm" /> </div> </div> <div class="row"> <div class="col-lg-6"> <div class="bs-component"> <table id="tblCourseList" class="table table-striped table-hover"> <thead> <tr> <th> <a href="#" ng-click="orderByField='id'; reverseSort = !reverseSort">Id <span ng-show="orderByField == 'id'"> <span ng-show="!reverseSort"><span class="glyphicon glyphicon-sort-by-order" aria-hidden="true"></span></span> <span ng-show="reverseSort"><span class="glyphicon glyphicon-sort-by-order-alt" aria-hidden="true"></span></span></span> </a> </th> <th> <a href="#" ng-click="orderByField='title'; reverseSort = !reverseSort">Title <span ng-show="orderByField == 'title'"> <span ng-show="!reverseSort"><span class="glyphicon glyphicon-sort-by-alphabet" aria-hidden="true"></span></span> <span ng-show="reverseSort"><span class="glyphicon glyphicon-sort-by-alphabet-alt" aria-hidden="true"></span></span></span> </a> </th> <th> <a href="#" ng-click="orderByField='location'; reverseSort = !reverseSort">Location <span ng-show="orderByField == 'location'"> <span ng-show="!reverseSort"><span class="glyphicon glyphicon-sort-by-alphabet" aria-hidden="true"></span></span> <span ng-show="reverseSort"><span class="glyphicon glyphicon-sort-by-alphabet-alt" aria-hidden="true"></span></span></span> </a> </th> <th>Edit | Delete</th> </tr> </thead> <tbody> <tr ng-repeat="course in courses | filter:query | orderBy:orderByField:reverseSort"> <td>{{ course.id }}</td> <td>{{ course.title }}</td> <td>{{ course.location }}</td> <td> <a href="#" ng-click="edit(course)" class="btn btn-default btn-sm">edit</a> <a href="#" ng-click="delete(course.id)" confirmation-needed class="btn btn-danger btn-sm">delete</a> </td> </tr> </tbody> </table> </div> </div> </div> <div> <div class="row"> <div class="col-lg-6"> {{ errMessage }} </div> </div> </div> </div>
Reblogged this on SutoCom Solutions.