Skip to main content

SharePoint hosted apps with Angularjs

Wow.. I thought i won't be able to manage to write a blog this month.

Hey guys today i am here with a something of that i have been planning since a long time. Some time back i tried to implement Angularjs with SharePoint. To be frank Angularjs is just awesome. You can have flawless feel while working with it. I don't know how will i manage to work with Angularjs in huge projects but i found it really really good to work with single page applications.

Let's have a look to the classic definition of Angularjs that you find when you google for Angularjs.

Angularjs

"AngularJS is a structural framework for dynamic web apps. It lets you use HTML as your template language and lets you extend HTML's syntax to express your application's components clearly and succinctly. Angular's data binding and dependency injection eliminate much of the code you would otherwise have to write."

SharePoint Apps

SharePoint Apps, I personally found it more developer friendly as well as with minimum configuration to deploy compared to Provider hosted apps. Lets have a look at the definition that Microsoft provides for SharePoint.

SharePoint-hosted add-ins are one of the two major types of SharePoint Add-ins. For an overview of SharePoint Add-ins and the two different types, see SharePoint Add-ins. Here's a summary of SharePoint-hosted add-ins:
They contain SharePoint lists, Web Parts, workflows, custom pages, and other components, all of which are installed on a sub web, called the add-in web, of the SharePoint website where the add-in is installed.

The only code they have is JavaScript on custom SharePoint pages.

Let's start with a small exercise wherein will create a simple CRUD operation for a product. Product list will have 4 columns
1. ID
2. Name
3. Cost
4. Description.

Lets start with a list of steps that we need to follow. Before we start lets have a look at prerequisites.
In order to create a SharePoint Hosted apps you will need a link to Developer's Template site. Site can be either on office365 or on premise. Your visual studio will need SharePoint/Office Project templates installed.

Angularjs with SharePoint



  1. Create a SharePoint List with above fields.
  2. Create a folder in SiteAssets library of SharePoint site and create a folder named AngularJsDemo.
  3. Add a text file to it named product-main
  4. What we do over here is we place all the Angularjs references in this particular file.
<link href="SitecollectionSiteAssets/AngularJsDemo/css/style.css" rel="stylesheet" type="text/css">
<script src="SitecollectionSiteAssets/AngularJsDemo/Library/angular.min.js" type="text/javascript"></script>
<script src="SitecollectionSiteAssets/AngularJsDemo/Library/angular-route.min.js" type="text/javascript"></script>
<script src="SitecollectionSiteAssets/AngularJsDemo/js/app.js" type="text/javascript"></script>
<script src="SitecollectionSiteAssets/AngularJsDemo/js/Services/baseSvc.js" type="text/javascript"></script>
<script src="SitecollectionSiteAssets/AngularJsDemo/js/Services/Products/product.js" type="text/javascript"></script>
<script src="SitecollectionSiteAssets/AngularJsDemo/js/Controllers/Product/all.js" type="text/javascript"></script>
<script src="SitecollectionSiteAssets/AngularJsDemo/js/Controllers/Product/add.js" type="text/javascript"></script>
<script src="SitecollectionSiteAssets/AngularJsDemo/js/Controllers/Product/edit.js" type="text/javascript"></script>

<div data-ng-app="productApp">
<div data-ng-view class="product-app"></div>
</div>

Also it includes required css files and references to the files where in we will right actual code.
















Css contains all the css files.
HTML Templates contains all the template files that will be rendered on screen.
Images contains all required images.
js contains Controller and Services folder wherein Controller contains all the controllers (Controllers are the actual back end of the that communicates with HTML Templates).
Services contains a file that contains a file that prepares data that we will send/receive data from/to server and will pass it to controller. Basesvc.js is the actual file that will communicate with SharePoint.
app.js acts as an route, Based on the url it renders HTML template in the div.

Below is the code that we need to write in each page.

all.html
<a href="#/add" class="add-new-button">Add New Product</a>
<div>
<table style="width:100%; margin-top:50px;" border="1">
<tbody>
<tr>
<th>Name</th>
<th>Cost</th>
<th>Description</th>
<th>Action</th>
</tr>
<tr data-ng-repeat="p in product">
<td>{{p.Name}}</td>
<td>{{p.Cost}}</td>
<td>{{p.Description}}</td>
<td>
<a href="#/edit/{{p.ID}}"><img style="height:20px; width:20px;" src="/sites/spin/rnd/SiteAssets/AngularJsDemo/images/edit.png" alt=""></a>
<a href="" data-ng-click="removeProduct(p)"><img style="height:20px; width:20px;" src="/sites/spin/rnd/SiteAssets/AngularJsDemo/images/delete.png" alt=""></a>
</td>
</tr>
</tbody>
</table>

</div>

edit.html
<h2>Edit Product</h2>
<hr>
<table>
<tr>
<td>Product Name</td>
<td>
<input type="text" data-ng-model="product.productName">
</td>
</tr>
<tr>
<td>Cost</td>
<td>
<input type="text" data-ng-model="product.cost">
</td>
</tr>
<tr>
<td>Description</td>
<td>
<textarea data-ng-model="product.description"></textarea>
</td>
</tr>
<tr>
<td></td>
<td>
<input type="button" value="Save" data-ng-click="updateProduct(product)">
</td>
</tr>

</table>

add.html
<h2>Add New Product</h2>
<hr>
<table>
<tr>
<td>Product Name</td>
<td>
<input type="text" data-ng-model="product.name">
</td>
</tr>
<tr>
<td>Cost</td>
<td>
<input type="text" data-ng-model="product.cost">
</td>
</tr>
<tr>
<td>Description</td>
<td>
<textarea data-ng-model="product.description"></textarea>
</td>
</tr>
<tr>
<td></td>
<td>
<input type="button" value="Save" data-ng-click="addProduct(product)">
</td>
</tr>

</table>

all.js

"use strict";
(function () {
angular.module("productApp")
.controller("addProductCtrl", ["$scope", "productService","$location", function ($scope, productService,$location) {
$scope.addProduct = function (product) {
productService.addNew(product)
.then(function(response){
console.log(response);
$location.path("/");
});
};
}]);

})();

edit.js

"use strict";
(function () {
angular.module("productApp")
.controller("editProductCtrl", ["$scope", "productService", "$routeParams","$location", 
function ($scope, productService, $routeParams,$location) {
productService.getById($routeParams.productId).then(function (response) {
$scope.product = {
productId : response.d.ID,
name : response.d.Name,
cost : response.d.Cost,
description : response.d.Description
};
$scope.editProduct = function(product){
productService.update(product)
.then(function(response){
$location.path("/");
});
};
});
}]);

})();

add.js

"use strict";
(function () {
angular.module("productApp")
.controller("allProductsCtrl", ["$scope", "productService",
function ($scope, productService) {
productService.getAll()
.then(function (response) {
                $scope.product = response.d.results;
});
$scope.removeProduct = function(p){
productService.remove(p.ID)
.then(function(response){
var productIndex = $scope.product.indexOf(p);
$scope.product.splice(productIndex,1);
});
};
}]);

})();

app.js
"use strict";
(function () {
angular.module("productApp", ["ngRoute"])
.config(["$routeProvider", function ($routeProvider) {
$routeProvider.when("/", {
templateUrl: "/sites/spin/rnd/SiteAssets/AngularJsDemo/HtmlTemplates/all.html",
controller: "allProductsCtrl"
}).when("/add", {
templateUrl: "/sites/spin/rnd/SiteAssets/AngularJsDemo/HtmlTemplates/add.html",
controller: "addProductCtrl"
}).when("/edit/:productId", {
templateUrl: "/sites/spin/rnd/SiteAssets/AngularJsDemo/HtmlTemplates/edit.html",
controller: "editProductCtrl"
});
}]);
})();

You need to register your different parts of code under a single module name. And also need to tell angularjs compiler about the the different controllers and views. So that the reason we place all the code in angular.module("productApp"... where productApp is the name of the div that we have added in produce-App.txt file. Also it acts as an name of module. You also need to register all the modules that you use in your project like ui-grid, any kind of animation etc. When ever you try to include any such thing into your project you need to register that particular module so that angularjs can easily identify it.

basesvc.js
"use strict";
(function () {
    angular.module("productApp")
        .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
        };
    }]);
})();

product.js
"use strict";
(function(){
angular.module("productApp")
.factory("productService",["baseSvc",function(baseService){
var listEndPoint = '/_api/web/lists';
var getAll = function(){
var query = listEndPoint + "/GetByTitle('Products')/Items?$select=ID,Name,Cost,Description";
return baseService.getRequest(query);
};
var addNew = function(product){
var data = {
__metadata: { 'type': 'SP.Data.ProductsListItem' },
Name : product.name,
Cost : product.cost,
Description: product.description
};
var url = listEndPoint + "/GetByTitle('Products')/Items";
return baseService.postRequest(data,url);
};
var getById = function(productId){
var query = listEndPoint + "/GetByTitle('Products')/GetItemById("+productId+")?$select=ID,Name,Cost,Description";
return baseService.getRequest(query);
};
var update = function (product){
var data = {
__metadata: { 'type': 'SP.Data.ProductsListItem' },
Name : product.name,
Cost : product.cost,
Description : product.description
};
var url = listEndPoint + "/GetByTitle('Products')/GetItemById("+product.productId+")";
return baseService.updateRequest(data,url);
};
var remove = function(productId){
var url = listEndPoint + "/GetByTitle('Products')/GetItemById("+productId+")";
return baseService.deleteRequest(url);
};
return{
getAll:getAll,
addNew:addNew,
getById:getById,
update:update,
remove:remove
};
}]);
})();

Now to add the solution to your page you just need to get the url of product-main.txt and place it into Content Editor webpart properties and that's it.

Angularjs in SharePoint hosted Apps

Below are steps that you need to follow in order to create SharePoint hosted apps.


  • Add new project to visual studio with below project template.


























  • Add a Module Named HTML Templates.












Now all all the HTML Template files that we created above to the Templates module. To do that right click on templates module and select Add Existing item. Select all the files that you want to add. 
Now open Element.xml file and check whether it has entries for all the uploaded files of not.
  • Upload all images to images folder.
  • css to Content folder.
  • Add all the js files to Scripts folder, try to keep the folder structure same.















Now its time to replace paths of each file that we have created so lets start with that.
Copy entire code of product-main.txt and paste it in Default.aspx.

Delete all the References that we added and drag and drop them from respective folders that you see in solution explorer.

You will also need to modify path's in app.js. Below is the code for it.
"use strict";
(function () {
angular.module("productApp", ["ngRoute"])
.config(["$routeProvider", function ($routeProvider) {
$routeProvider.when("/", {
   templateUrl: "../templates/all.html",
controller: "allProductsCtrl"
}).when("/add", {
   templateUrl: "../templates/add.html",
controller: "addProductCtrl"
}).when("/edit/:productId", {
   templateUrl: "../templates/edit.html",
controller: "editProductCtrl"
});
}]);
})();

Basically we are just replacing path to html pages.

Another important change that we need to do is in basesvc.js file. We just need to get the SPAppWebUrl instead of _spPageContextInfo.webAbsoluteUrl.  You can find SPAppWebUrl in the query string as parameter. Make sure you trim url after #/ because that is appended by angularjs.

Below is the code of basesvc.js file now.

"use strict";
(function () {
    angular.module("productApp")
        .factory("baseSvc", ["$http", "$q", function ($http, $q) {
            var temp = decodeURIComponent(manageQueryStringParameter('SPAppWebUrl'));
            var baseUrl = temp.substr(0, temp.indexOf('#'));
        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
        };
    }]);
})();

function manageQueryStringParameter(paramToRetrieve) {
    var params =
    document.URL.split("?")[1].split("&");
    var strParams = "";
    for (var i = 0; i < params.length; i = i + 1) {
        var singleParam = params[i].split("=");
        if (singleParam[0] == paramToRetrieve) {
            return singleParam[1];
        }
    }
}

Now you will need to add list to SharePoint app. Right click on project select Add new Item >  SharePoint List and add required fields to the same. This will re create list each time SharePoint app is deployed.

That's it now just right click project and hit deploy. You will find the App deployed to the dev site.
Your app will be in App in Testing library of the dev site. You can click on the app from site contents.


Make sure when ever you need to re-deploy your app you change the version of app from AppManifest.xml. This will minimize chances of error in deployment.

Regards,
Keyur Pandya

Comments

  1. Can you provide a complete code package zipped up for AngularJS with SharePoint? or at least files like images, style.css etc. you are referring therein?

    ReplyDelete
  2. Can you, please, clarify the following step?
    "Copy entire code of product-main.txt and paste it in Default.aspx"
    Replace all content of default.aspx?

    ReplyDelete
  3. This is the only simplest article I found to learn how to use AngularJS 1 with SharePoint online. Please provide the style.css

    ReplyDelete
  4. Questo tipo di aspetto sembra assolutamente migliore. Queste minuscole informazioni vengono prodotte in aggiunta alla massiccia comprensione del passato storico. Mi piace molto questo genere. app per bloccare le chiamate

    ReplyDelete

Post a Comment

Popular posts from this blog

Identity client runtime library (IDCRL) did not get a response from the login server.

Recently I was doing some testing with a background PowerShell and encountered a weird error. “Identity client runtime library (IDCRL) did not get a response from the login server”. The error that you might encounter while working with PowerShell. This error is very misleading when it comes to identifying what could go wrong. After doing quite a good amount of research below are the probable causes for the error. Invalid Credentials MFA (Multi-Factor Authentication) Manage security defaults. Solutions Invalid Credentials Check if your credentials are wrong. Especially if you are using variables. MFA (Multi-Factor Authentication) Check if MFA is enabled on the account which you are using. These only affect you badly if you are developing PowerShell for a background Job. Go to Microsoft 365 admin center Users -> Active users -> Select the user -> Manage multifactor authentication -> Select the user -> Disable multi-factor authentication. M...

Business Data Connectivity

I came to a requirement wherein I was supposed to get data from an 3 rd party portal using API’s and then bring them to SharePoint server. The first approach that I finalized was just to make BDC solution that will get data from 3 rd party portal and will deploy it to SharePoint. How to Create BDC solution in SharePoint? I found below link that is having really great description about hot to create and deploy the BDC solution to SharePoint. http://www.c-sharpcorner.com/uploadfile/hung123/creating-business-data-connectivity-service-using-visual-studio-2010/ After creating an POC I came to know that BDC model cannot be deployed on Multi tenant farm. So what can be done next? After some amount of googling I came to know that we can create BDC solution using WCF services also. So I created a WCF service solution that acted as a wrapper that used to fetch data from the portal. We can them publish that service to IIS or Server and use the servic...

Site Design Tasks, Power Automate and Modern Sites

S harePoint Site templates are now replaced with Site designs in Modern Sites. We can create custom site designs using some json script(s). Site Design allows to create lists, create list views, apply theme, customize navigation, create content types, create site columns and so on. Click here to see JSON scheme reference for creating custom site design.  Endpoint to apply site design as a part of flow is as below. _api/Microsoft.Sharepoint.Utilities.WebTemplateExtensions.SiteScriptUtility.AddSiteDesignTaskToCurrentWeb We must use “Send Http Request to SharePoint” action to make an API call to SharePoint. SiteDesignId must be part if body, this is GUID of the SharePoint site design you need to apply. We can line up sequence of calls if we need to apply multiple site designs to a single site. We can now wait for few mins and all our sited designs will get applied or we can also check the status by making another API call with below endpoint. You just need to pass the Apply Desig...