10/17/15

SharePoint Choice Field Options with AngularJS $resource

A SharePoint data list can have a field of type = Choice which is used to provide the options for a dropdown control. When building SharePoint apps with AngularJS, we need to build the dropdown control with the options that are defined by the field’s metadata. Let’s see how we can go about to get this information.

In order to fetch the options from the Choice field from a given data list, we can use the REST APIs with OData operations. To show how this can be done using the AngularJS $resource service, we can take a look at an example in which we have a Task data list with a status field (type = Choice) with the following options:

Status (Choice) field options
Pending
In-Progress
Complete

We start by building an AngularJS service which uses the $resource service for our API calls. Note that we could also use the $http service as well.  Our service defines the $resource operation as follows: (note the relative path and the use of the getbytitle API route to get list information)

 var apiUrl = "../_api/web/lists/getbytitle('task')/:route";
       
        var api = $resource(apiUrl, { route: '@route' }, {
            //Other methods and routes
            status: { method: 'GET',params: {route:'fields'},isArray:false}        
        })

When making the call to get the data list status information, we need to send on the request an OData parameter to tell the API that we are interested in getting the information from a particular field. In our case, we need the metadata from the Status field which includes the choices that are available. Note that the Choice field does not provide a text/value pair. It only provides the text part of the options. This must be used for the value/text properties of the drop-down control. When updating the model and consequentially making a Create/Update API calls, we must persist the text value as this is the expected data (constraint) on the data list.

Our service and the Status method implementation to get the field's metadata looks like this:

function svcTask($q, $resource) {
        var apiUrl = "../_api/web/lists/getbytitle('task')/:route";
       
        var api = $resource(apiUrl, { route: '@route' }, {
            //Other methods and routes
            status: { method: 'GET', params: {route:'fields'}, isArray:false}        
        })
      
        var service = {
            /*
              name:status
              gets the options from a choice field type (sharepoint)
            */
            status: function () {
                var deferred = $q.defer();
 
 //gets the field by title
                var params = { '$filter': "Title eq 'Status'" };
               
api.status(params, function (data) {
                    statusOk(data);
                },
                    function (err) {
                        deferred.reject(err);
                    }
                );

                function statusOk(data) {
                    var result = data.value[0].Choices;
                    var list = [];
                    for (var idx = 0; idx < result.length; idx++) {
                        var item = { code: result[idx], name: result[idx] };
                        list.push(item);
                    }

                    service.statusList = list;
                    deferred.resolve(list);
                }
                return deferred.promise;
            }          
        }

        return service;
    }

A few notes about this code:
  • The getbytitle API is used to query a list metadata as well as data.
  • The Fields route segment is used to tell the API that we are interested in requesting field metadata information. This is where the options are defined.
  • The $filter OData parameter is used to tell the API which field we need to query by filtering on the title property.
  • The choices property is contained on the response Value property and has a results array.



The request is made by using the OData $filter request parameter. If the response is OK, the statusOk function is called. Otherwise an error is return by calling deferred.reject(err).

In the statusOk function, the code iterates thru the list and builds a JSON array that contains the value/text pairs. This is the model for our dropdown. Since there is a bit of data mapping/transformation that we need to do, and we want to encapsulate this away from the controller, the Status method returns a deferred promised. This allows us to handle the asynchronous request and perform the data mapping yet still keeping the asynchronous behavior. We then return the transformed data by calling deferred.resolve(list). 

The controller can now consume this service by calling the status method as shown below:

    function ctrlTask(svcTask) {

        var ctrl = this;
        ctrl.statusOptions = [];   //model for the status dropdown

        //load the status options waiting on a promise
        svcTask.status().then(function (data) {
            ctrl.statusOptions = data;
        },
            function (err) {
                $log.error(err);

            }
        );

    }

The ctrl.statusOptions is the model that holds the options for our dropdown on the view.

Finally, this is how the module is defined with both service and controller:

(function (angular) {
    'use strict';
    var appName = 'app';
    var app = angular.module(appName,['ngRoute', 'ngResource']);
    app.factory('svcTask', ['$q', '$resource', svcTask]);
    app.controller('ctrlTask', ['svcTask', ctrlTask]);

    function svcTask($q, $resource) {
        //...           
    }

    function ctrlTask(svcTask) {
        //...
    }


})(angular);


I hope this tip helps you build SharePoint Apps with AngularJS.