2

I have a scenario of fetching data from multiple SharePoint 2013 lists using REST API and Angularjs. I am able to fetch the data successfully from one of the SharePoint list but my requirements is to fetch the data from multiple lists on the page load. I am using a provider hosted app to fetch the data from host web. I have 2 methods for calling 2 separate lists. I am getting the results from first method successfully but when the second method is called after the execution of 1st method. I am getting a time out error. It seems like i cannot call the 2 methods one after the other. Below is my code, could anyone please help me if i am missing something or if there is any other way to fetch the data from multiple SharePoint lists.

Method 1: fetch Data from List 1

var query = listEndPoint + "/getbytitle('CandidateList')/items?$select=ID,FirstName,MiddleInitial,LastName,EmailAddress,PrimaryPhoneNo,ProfileImage,Address,State,Country,CurrentTitle,CurrentCompany,LastActivityModifiedBy,LastActivityModifiedDate,DeletedStatus&@target='" + hostweburl + "'";

    var getCandidates = function (query, queryCandidateNotes) 
                    {
                    alert('getRequest');
                    var scriptbase = hostweburl + "/_layouts/15/";
                    var deferred = $q.defer();
                    // Load 15hives js files and continue to the successHandler    
                    $.getScript(scriptbase + "SP.Runtime.js",
                        function () {`enter code here`
                            $.getScript(scriptbase + "SP.js",
                                function () {
                                    $.getScript(scriptbase +"SP.RequestExecutor.js",
                                         function () {
                                             var executor = new SP.RequestExecutor(appweburl);
                                             executor.executeAsync({
                                                 url: query,
                                                 method: "GET",
                                                 headers: { "Accept": "application/json; odata=verbose" },
                                                 success: successHandler,
                                                 error: errorHandler
                                             });
                                             //deferred.resolve();
                                         });
                                });
                        });

                    function successHandler(data) {
                        var jsonObject1 = JSON.parse(data.body);

                        deferred.resolve(jsonObject1);

                    }

                    function errorHandler(data, errorCode, errorMessage) {
                        alert('Error1:' + errorMessage + data.body);
                    }
                    // Get 
                    return deferred.promise;

                    //Candidate Details Ends
                };

Method 2: fetch Data from List 2

    var queryCandidateNotes = listEndPoint + "/getbytitle('CandidateNotes')/items?$select=Title,CandidateId&@target='" + hostweburl + "'";

 // Get All Candidate Notes
            var getCandidateNotes = function (queryCandidateNotes) {
                alert('getCandidateNotesRequest');
                var scriptbase = hostweburl + "/_layouts/15/";
                var deferred2 = $q.defer();
                // Load 15hives js files and continue to the successHandler    
                $.getScript(scriptbase + "SP.Runtime.js",
                    function () {
                        $.getScript(scriptbase + "SP.js",
                            function () {
                                $.getScript(scriptbase + "SP.RequestExecutor.js",
                                     function () {
                                         var executor = new SP.RequestExecutor(appweburl);
                                         executor.executeAsync({
                                             url: queryCandidateNotes,
                                             method: "GET",
                                             headers: { "Accept": "application/json; odata=verbose" },
                                             success: successHandler,
                                             error: errorHandler
                                         });
                                         //deferred.resolve();
                                     });
                            });
                    });

                function successHandler(data) {
                    var jsonObject2 = JSON.parse(data.body);
                    //var results2 = jsonObject2.d.results;
                    deferred2.resolve(jsonObject2);
                    //alert('2nd success:' + jsonObject2);
                    //return jsonObject2;
                }

                function errorHandler(data, errorCode, errorMessage) {
                    alert('Error2 :' + errorMessage + data.body);
                }
                // Get 
                return deferred2.promise;

};

Method 3: Calling method 2 after method 1

   var getRequest = function (query, queryCandidateNotes) {


                var deferred = $q.defer();
                $.when(getCandidates(query, queryCandidateNotes)).then(function (data) {

                    alert('Success1:' + data);

                                           $.when(getCandidateNotes(queryCandidateNotes)).then(function (data1) {
                        deferred.resolve(data);
                        alert('Success2:' + data1);
                    });
                    })
                return deferred.promise;
            };

            return {
                getRequest: getRequest

            };

        }]);
})();
user545359
  • 413
  • 5
  • 16
  • 29

1 Answers1

3

$.when is not appropriate here, utilize $q.all that combines multiple promises into a single promise that is resolved when all of the input promises are resolved.

Example

app.controller('listController', function ($scope, $q, listService) {
    SP.SOD.executeFunc('SP.RequestExecutor.js', 'SP.RequestExecutor', function () {

        $q.all([listService.getListItems('Documents'), listService.getListItems('Site Pages')]).then(function (data) {
            $scope.documentsItems = data[0].d.results;
            $scope.sitePagesItems = data[1].d.results;

        });

    });
});

where listService is a service for getting list items:

app.factory('listService', ['$q', function ($q) {
    var getListItems = function (listTitle) {
        var d = $q.defer();
        JSRequest.EnsureSetup();
        var hostweburl = decodeURIComponent(JSRequest.QueryString["SPHostUrl"]);
        var appweburl = decodeURIComponent(JSRequest.QueryString["SPAppWebUrl"]);

        var queryUrl = appweburl + "/_api/SP.AppContextSite(@target)/web/lists/getByTitle('" + listTitle + "')/items?@target='" + hostweburl + "'";
        var executor = new SP.RequestExecutor(appweburl);
        executor.executeAsync({
            url: queryUrl,
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: function(data, textStatus, xhr) {
                d.resolve(JSON.parse(data.body));
            },
            error: function(xhr, textStatus, errorThrown) {
                d.reject(JSON.parse(xhr.body).error);
            }
        });
        return d.promise;
    };

    return {
        getListItems: getListItems
    };
}]);

Solution description

enter image description here

Default.aspx

<asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
    <script type="text/javascript" src="../Scripts/jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>

    <SharePoint:ScriptLink Name="sp.js" runat="server" OnDemand="true" LoadAfterUI="true" Localizable="false" />
    <meta name="WebPartPageExpansion" content="full" />

    <!-- Add your CSS styles to the following file -->
    <link rel="Stylesheet" type="text/css" href="../Content/App.css" />

    <!-- Add your JavaScript to the following file -->

    <script type="text/javascript" src="../Scripts/listService.js"></script>
    <script type="text/javascript" src="../Scripts/App.js"></script>

</asp:Content>

and

<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
    <div ng-app="SPApp" ng-controller="listController">
    </div>
</asp:Content>

App.js

'use strict';

(function() {

    var app = angular.module('SPApp', ['SPApp.services']);

    app.controller('listController', function ($scope, $q, listService) {

        executeOnSPLoaded(function () {
                $q.all([listService.getListItems('Documents'), listService.getListItems('Site Pages')]).then(function (data) {
                    $scope.documentsItems = data[0].d.results;
                    $scope.sitePagesItems = data[1].d.results;
                });
        });


    });

})();



function executeOnSPLoaded(loaded) {
    JSRequest.EnsureSetup();
    var hostweburl = decodeURIComponent(JSRequest.QueryString["SPHostUrl"]);
    var scriptbase = hostweburl + "/_layouts/15/";
    $.when(
        //$.getScript(scriptbase + "SP.Runtime.js"),
        $.getScript(scriptbase + "SP.js"),
        $.getScript(scriptbase + "SP.RequestExecutor.js"),
        $.Deferred(function (deferred) {
            $(deferred.resolve);
        })
    ).done(function () {
        loaded();
    });
}

listService.js

'use strict';


angular.module('SPApp.services',[])
.factory('listService', ['$q', function ($q) {
    var getListItems = function (listTitle) {
        var d = $q.defer();
        JSRequest.EnsureSetup();
        var hostweburl = decodeURIComponent(JSRequest.QueryString["SPHostUrl"]);
        var appweburl = decodeURIComponent(JSRequest.QueryString["SPAppWebUrl"]);

        var queryUrl = appweburl + "/_api/SP.AppContextSite(@target)/web/lists/getByTitle('" + listTitle + "')/items?@target='" + hostweburl + "'";
        var executor = new SP.RequestExecutor(appweburl);
        executor.executeAsync({
            url: queryUrl,
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: function(data, textStatus, xhr) {
                d.resolve(JSON.parse(data.body));
            },
            error: function(xhr, textStatus, errorThrown) {
                d.reject(JSON.parse(xhr.body).error);
            }
        });
        return d.promise;
    };

    return {
        getListItems: getListItems
    };
}]);
Vadim Gremyachev
  • 57,952
  • 20
  • 129
  • 193
  • Hi Vadim, thanks for your reply. I will implement your suggestions and try it out. Before changing my code I have a few questions. I am loading sp.requestexecutor.js file before executing async. Also, there are couple more .js files. Could you please let me know if I need to load the js files in the controller as you mentioned above. Thanks again. – user545359 Jan 11 '16 at 14:28
  • Usually the loading of `sp.js` and `sp.runtime.js` are not required since those libraries are loaded by default in App page. But `sp.requestexecutor.js` should be loaded explicitly, basically it does not matter how it is loaded, using `$.getScript` function from jQuery like in your example or using SharePoint SOD capabilities like in mine – Vadim Gremyachev Jan 11 '16 at 14:38
  • Hello Vadim, I tried your suggestion yesterday. Actually my controller is in one JavaScript file and my getlist service is in another. Due to this when i initiated the SP.RequestExecutor in service script I get an error SP is undefined. Also before trying your solution I tried multiple calls to Sharepoint List through provider hosted app by creating 2 separate services and controllers the first call executes properly but when the second call is trying to execute I get an error "request timed out" Is there a limitation on sending the number of calls to SharePoint Rest Api? – user545359 Jan 12 '16 at 14:06
  • Hi Vadim, I have observed that whenever I make multiple calls to sharepoint list I get an error request timed out after the first call is executed successfully. Do I need to close the async connection to rest endpoint before I execute the second call? I am new to angularjs and rest api. I may be missing something here, could you please help me out with this issue? This has been troubling since longtime. – user545359 Jan 13 '16 at 13:37
  • Unfortunately i never experienced such kind of error (timeout) in REST requests. I would suggest to try to perform only a second request.. to determine whether this timeout error occurs or not Regarding closing the connection, no, there is no need to perform any connection close. P.S. The example i have provided in my answer works for me. – Vadim Gremyachev Jan 13 '16 at 13:49
  • Thanks for the quick response. Do you have 2 separate js files for controller and service? Because when I called the SP.RequestExecutor in my controller and when the debugger hits the service js it errors out var executor initiation saying "SP" is undefined. Do I need to refer any scripts before executing this line? I believe this may be happening because the SP.Requestor is initiated in one js file and it is not finding the reference in my service script. Could you please let me know. – user545359 Jan 13 '16 at 14:03
  • It means sp.js is not yet loaded once the service method is invoked.. just a moment, i'll update the answer to address this issue – Vadim Gremyachev Jan 13 '16 at 14:07
  • If possible could you please include all the references so it will be very helpful. Also, mine is a provider hosted app. Does it matter with the approach you suggested or it will work for all the app model scenarios? – user545359 Jan 13 '16 at 14:14
  • 1
    Thank you so much Vadim, The solution worked like a charm.I have another issue,On page load i am able to load data from 2 lists.But when the user click on a menu tab i have created another controller which calls the same listservice to fetch data from different list.Could you please help me with fetching the data from lists on user interaction Ex: when user clicks tab1 -list1,tab2-list2? Also do we need to initiate the SP.RequestExecutor everytime i use the controller or can we declare it globally on pageload and reuse the same object everytime user performs list operations through controller. – user545359 Jan 14 '16 at 04:22
  • If you are using ui-router, you can pass the dynamic value of the element being clicked with ui-router's resolve through $stateParams. – CR Rollyson Dec 14 '16 at 03:01