awesome.jpg

Using Angular to Create a Dynamic SharePoint List Form

A while back a customer approached us with some fairly significant updates to a very complex custom SharePoint form we had written for them. After reviewing the change request, we recognized that the architecture wasn’t really what the customer needed. Apparently, they had been queueing up fields they wanted to add or remove for a while and were just now getting the budget to request the changes. What the customer really needed was the ability to add and remove fields from the form without the involvement of a developer. However, they weren’t able to use the OOTB form because of validation rules, dynamic enablement and requirement of fields, role-based security at the field level, and they didn’t like the overall experience of large forms in OOTB SharePoint (who does, really?). To complicate the issue, the customer’s IT department had made the law of the land that only sandbox solutions were allowed in their farm. This means that any solution we come up with had to be sandbox compliant.

In the past, this is when we would reach for InfoPath. While not something that I would categorize as end-user friendly, it was certainly capable of the tasks at hand and changes could be done without developer involvement. However, with Microsoft’s nebulous (at that time) comments around InfoPath’s support being limited, we couldn’t in good conscience try to convince the customer to invest in a solution that had a shelf life. Access Web Forms was a potential solution but was eliminated due to some other unique business rules to the project.

We had recently done something similar for another customer using a custom form that through its evolution had been transformed from a similar static form to a fully dynamic form that was driven by another SharePoint list. The other list had a row for each field in the list, the type of field, and some additional information that drove additional functionality. When someone opened the ‘new’ or ‘edit’ form for the list the code would use JSOM to query the list with the field information and then dynamically render a HTML form control for each row in the fields list using Knockout.js.

We liked Knockout for the previous project, but we found the performance to be suboptimal; the interaction with JSOM was clunky, and dynamic form validation was arduous (thankfully the requirements for form validation for that project were light). We knew going into this new project that we should take a serious look at other frameworks to see if we could find a better solution. So we made a small spike at the beginning of the project to review Angular and Backbone. There were others we could look have looked at, but those were the two forerunners and we needed to keep the spike small.

Backbone is really slick. Especially if you are doing data grid type tables. However, I found that the binding model wouldn’t work well for us since our table schema wasn’t well defined when the form is loaded. While it was certainly possible, it felt forced.

One of the attractions of (and the reason we chose) Angular was the fact that it is highly customizable. Specifically, the ability to define custom ‘controls’ (called Directives in Angular-speak) was extremely attractive as it closely mimicked what we were trying to accomplish. While this was a bit of a naïve interpretation of how directives work, in the end we found it worked quite well.

But enough background, let’s look at some code.

First, we need to define the list that holds all the fields in our form. At the basic level, we need the following:

Field NameField Purpose
Display NameThe name of the field to be shown in the HTML form
Internal NameThe static name used to address the field in the resultant queries
Field TypeThe type of control to render

 

On a side note, there is something metaphysically poetic about a SharePoint list that is used to describe another SharePoint list. Anyways…

For the Field List, we could have a number of other columns that could define more functionality. For example, we could have a required check box, a validation rule, an enablement rule so other controls make a field visible or required, a people picker to define who can (or cannot) edit a field, and so forth.

Next we need to create a form that calls the angular code. There are a couple of ways to do this. Because we were elevating the Field List list instance via a sandbox WSP, we just added a module to our project in Visual Studio, but it could be done through SP Designer or however you feel comfortable. The form will need to have a script reference for Angular.js at a minimum (it’s not required to have a reference to jQuery but its recommended). You will also need a JavaScript file in SharePoint to hold your Angular code. There are a few best practices at Angular’s site around how to create and structure your .js and partial files. For this blog post, I am not going to follow those rules for the sake of presentation, so you should read up on those best practices before you get too far.

Once the form is stored in SharePoint, it’s time to get Angular to talk to SharePoint. The first steps are to use the ng-app and ng-controller directives. For this blog post, I put the ng-app directive on the body element and the ng-controller directive on a div nested in the form like so:

<body data-ng-app="demoApp">
  <form id="fieldsForm" name="fieldsForm" runat="server">
<div data-ng-controller="demoController"></div>
</form>
</body>

The ng-app directive tells Angular to look for a module called “demoApp”. Once the module is found then Angular will initiate the controller called “demoController.” The ng-app code is pretty simple and can be as simple as:

var demoApp = angular.module(‘demoApp’, []);

However, I did a little extra work to set the Accept HTTP header globally so I don’t have to set it for every GET request. So my module declaration looks like this:

var demoApp = angular.module('demoApp', []).run(function ($http) {
  $http.defaults.headers.common.Accept = "application/json;odata=verbose";
});

Next, define a controller and name it “demoController”:

demoApp.controller('demoController', function ($scope, fieldsFactory) {
  init();

  function init() {
  };
});

I picked up the habit of using an init() function in the controller from Dan Wahlin, but it’s not necessary. It’s just a way to keep the code a bit easier to understand six months from now.

Now that the framework is in place, it’s time to get the form to pull the list of fields from the Fields List. To do this I am going to use an Angular factory. The factory will return a promise that will look in the current site for a list called “Fields List.” Pull down all the items in that list, and return the raw JSON. This is pretty simple with Angular’s $http implementation.

demoApp.factory('fieldsFactory', function ($q, $http) {
  var webUrl = _spPageContextInfo.webServerRelativeUrl + "_api/";
  var factory = {};

  //Loads the fields to render on the form
  factory.getFields = function () {
    var restQueryUrl = webUrl + "web/lists/getbytitle('Fields List')/Items";
    return $http.get(restQueryUrl);
  };

  return factory;
});

Note the use of _ spPageContextInfo. This is a special JavaScript object that SharePoint provides on each page. To use it you have to add a couple special script references in your HTML form. I put them inside the <form> element at the very top to ensure they get executed first.

<div id="div_form1" style="display: none;">
  <SharePoint:ScriptLink ID="SharePointJS" Name="SP.js" runat="server" LoadAfterUI="true" Localizable="false" />
  <asp:ScriptManager id="ScriptManager" runat="server" EnablePageMethods="false" EnablePartialRendering="true" EnableScriptGlobalization="false" EnableScriptLocalization="true" ScriptMode="Release" />
  <SharePoint:FormDigest ID="FormDigest1" runat="server" />
  <WebPartPages:SPWebPartManager ID="m" runat="Server" /></div>

Next, we need to add code to the controller’s init() function to call the promise and parse the results into an object on $scope like so:

function init() {
  fieldsFactory.getFields().then(function (result) {
    var results = result.data.d.results;
    $scope.fields = [];

    for (var x = 0; x < results.length; x++) {
      var field = initializeField(results[x]);
      $scope.fields.push(field);
   }
  });
};

function initializeField(result) {
  var retVal = {};
  retVal.Id = result.Id;
  retVal.FieldDisplayName = result.Title;
  retVal.FieldInternalName = result.InternalName;
  retVal.FieldType = result.FieldType;
  return retVal;
};

Next we add a <div> inside the <div> with the controller directive added. This <div> will have an ng-repeat directive that will loop over each of the fields in $scope.fields. It will then use ng-include to pull in an external HTML file that has the control templates we want to render based off Field Type.

<div data-ng-controller="demoController">
<div data-ng-repeat="field in fields">
<div data-ng-include="'partials/controls.htm'"></div>
</div>
</div>

Finally, we use ng-switch in our partial file to render different <span> elements based off Field Type.

<div data-ng-switch='field.FieldType'>
  <span data-ng-switch-when='Single Line of Text'>
<div>{{field.FieldDisplayName}}</div>
<input name="{{field.FieldInternalName}}" data-ng-model="field.value" type='text' />
  </span>
  <span data-ng-switch-when='Multiple Lines of Text'>
<div>{{field.FieldDisplayName}}</div>
<textarea name="{{field.FieldInternalName}}" cols='80' rows='5' data-ng-model="field.value" />
  </span>
  <span data-ng-switch-when='Number'>
<div>{{field.FieldDisplayName}}</div>
<input name="{{field.FieldInternalName}}" data-ng-model="field.value" />
  </span></div>

And the end result:

AngularDemo

While this is an overly simplistic example you can see the potential of this approach. I do not show saving or editing a list item but that is pretty simple using Angular’s $http.post and $http.put methods. Using Angular to call SharePoint and render a dynamic form client side is very powerful. This requires no code to be elevated to SharePoint (although a sandbox WSP does make things simpler) and could be done in a pure Office 365 environment without a sandbox solution.

Next blog post I will show how to do dynamic form validation and control enablement using this architecture.

You can download the source code for this blog post below:

AngularBlog

Lane GoolsbyUsing Angular to Create a Dynamic SharePoint List Form

3 comments

Join the conversation
  • Kelly Rusk - May 11, 2015 reply

    How would we handle complex fields, such as Managed Metadata, Date/Time, Lookup, etc?

    sakti das - April 27, 2018

    Great demo.. if we can add module to check which fields i want to render , read only etc.. router for edit .. that would be great.. need to keep the use case and design simple enough..

  • Craig M. Wales - December 10, 2015 reply

    How do you handle a angular post but not have it post the default asp.net form as well?

Join the conversation

This site uses Akismet to reduce spam. Learn how your comment data is processed.