0

I'm working on a small application where we basically have groups, sub-groups and members of the sub-groups. The group in this case are the Risk Areas which are basically 3 in number. Given IDs of 1,2 and 3 respectively. Each of the risk areas have sub-groups which are called family Units and for each family unit, there're family members. A risk area could have about 127 Family units each with its respective members.

I'm having the challenge of retrieving family members grouped under each family units. I get results but most of them are undefined. In a case where a Risk Area has 127 family units, I'm only able to retrieve about 24 family units with their members with over 100 undefined. Below is my controller

My Controller

    .controller('familyRiskCtrl', ['$scope', 'familydataService', '$routeParams', function ($scope, familydataService, $routeParams) {
        $scope.familysR = [];

        $scope.currentPage = 1;
        $scope.itemsPerPage = 5;

        getDataRiskArea();

        // Gets family by risk area
        function getDataRiskArea() {
            familydataService.getFamilysByRiskArea($routeParams.id).then(function (result) {
                $scope.familyR = result;
                console.log(result);

                // variable to hold the families
                var familyUnit = [];                     

                // Get family by FSU Id (FuId)
                angular.forEach(result, function (value, key) {

                    familydataService.getFamilyByFuId(value.FuId).then(function (resulti) {

                        var familyResult = resulti;
                        var FuIds = resulti[key].FuId;

                        console.log(key);

                        // Push the array object to families
                        familyUnit.push({
                            Id: value.FuId,
                            FamilyUnitName: value.FamilyUnitName,
                            Families: familyResult
                        })
                    });
                });

                // console.log(families);
                console.log(familyUnit);
                $scope.FamilyUnit = familyUnit;
            });
        }

        $scope.sortBy = function (column) {
            $scope.sortColumn = column;
            $scope.reverse = !$scope.reverse;
        };
    }])

And below is the service used. When I console logged the results, I realized that the risk area got all the distinct Family Unit Ids but on trying to get members under the distinct family units, It's only able to retrieve a few throwing error for over 70% of the remaining data not pulled.

Please note, I used the value of the angular.forEach() to track the distinct FuIds in the array when was later used to fetch members of the family unit.

My Service

(function () {
'Use Strict'

angular
    .module('app')
    .factory('familydataService', ['$http', '$q', function ($http, $q) {
        var service = {};
        // Gets the Family by Risk Area ID
        service.getFamilysByRiskArea = function (id) {
            var deferred = $q.defer();
            $http.get('/Family/FamilyByRisk/' + id).then(function (result) {
                deferred.resolve(result.data);
            }, function () {
                deferred.reject();
            });
            return deferred.promise;
        };

        // Get family by FuID
        service.getFamilyByFuId = function (id) {
            var deferred = $q.defer();
            $http.get('/Family/FamilyByFuID/' + id).then(function (result) {
                deferred.resolve(result.data);
            }, function () {
                deferred.reject();
            });
            return deferred.promise;
        };

        return service;
    }]);
})();

And below is what my console.log looks like enter image description hereenter image description here

From the above, there're 127 Family Units retrieved by the risk area ID out of which the 24 Family Units with their members were retrieved with the remaining undefined.

Guzzyman
  • 561
  • 6
  • 16
  • 37

1 Answers1

0

You are trying to parse a value Id: value.FuId, in a nested promise that is an async function.

  • foreach loop will continue with the next call even if familydataService.getFamilyByFuId(value.FuId) hasn't finish
  • return method of the promise has its own block scope

For those 2 reasons Code will break due to out of scope on your value variable

Here you create an Http call with 127 results and then a call for each one. This is a total of 128 requests to get an object. With respect to the maximum size of data from all possible requests by $routeParams.id in total. I propose to create an API call returning a list of DTOs with proper data in total, letting timecost to server in a single call using indexes on tables or||and a view.

Although if you are figured that specific way try $q.all. A cheat is that it resolves in exact order giving you the advantage of same array indexing or by name of the promise see API Reference down the end.

Returns a single promise that will be resolved with an array/hash of values, each value corresponding to the promise at the same index/key in the promises array/hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.

$q.all({ a: funa('load'), b: funb('load') }).then(function (r) {
    console.log(r, r.a, r.b);
})
$q.all([ funa('load'),funb('load') ]).then(function (r) {
    console.log(r, r[0], r[1]);
})

try create and call a function with the second promise parsing the var you need

function getDataRiskArea(id) {
    familydataService.getFamilysByRiskArea(id).then(function (result) {
        $scope.familysR = result;
        let unique = result;
        // Get family by FSU Id (FuId)
        $scope.getFamily(unique);
    }).finally(function () {
        console.log($scope.FamilyUnit);
    });
}
function getFamily(RiskRes) {
    let loop = RiskRes;
    var promises = [];
    for (var i = 0; i < loop.length; i++) {
        let callid = loop[i]['FuId'];
        var promise = familydataService.getFamilyByFuId(callid);
        promises.push(promise);
    }
    //here is by indexing
    $q.all(promises).then(data => {
        //Here Data is an array of results by its call 
        //So the loop order has the same results here 
        console.log('All promises have resolved', data);
        let res = [];
        for (var i = 0; i < loop.length; i++) {
            let obj = {
                Id: loop[i]['FuId'],
                FamilyUnitName: loop[i]['FamilyUnitName'],
                Families: data[i]
            };
            res.push(obj);
        }
        $scope.FamilyUnit = res;
    });
}

Here are some great refs to give a check Chain multiple promises , promise angular $q.all , and a working snippet.

'Use Strict';

function run($rootScope) {}
angular.module('app', [
]).controller('familyRiskCtrl', function ($scope, familydataService, $q) {
    $scope.familysR = [], $scope.tempUniqueIds = [];
    $scope.currentPage = 1;
    $scope.itemsPerPage = 5;
    // Gets family by risk area
    function getDataRiskArea(id) {
        familydataService.getFamilysByRiskArea(id).then(function (result) {
            $scope.familysR = result;
            let unique = result;
            // Get family by FSU Id (FuId)
            $scope.getFamily(unique);
        }).finally(function () {
            // this will be undefined to present you that this is async too 
            // and initiallized at the end  of getFamily
            console.log('FamilyUnit:',$scope.FamilyUnit);
        });
    }
    function getFamily(RiskRes) {
        let loop = RiskRes;
        var promises = [];
        for (var i = 0; i < loop.length; i++) {
            let callid = loop[i]['FuId'];
            var promise = familydataService.getFamilyByFuId(callid);
            promises.push(promise);
        }
        $q.all(promises).then(data => {
            //Here Data is an array of results by its call 
            //So the loop order has the same results order here 
            //That represents that data[i] is a result of loop[i]['FuId'] call
            //console.log('All promises have resolved', data);
            let res = [];
            for (var i = 0; i < loop.length; i++) {
                let obj = {
                    Id: loop[i]['FuId'],
                    FamilyUnitName: loop[i]['FamilyUnitName'],
                    Families: data[i]
                };
                res.push(obj);
            }
            $scope.FamilyUnit = res;
        });
    }

    $scope.getDataRiskArea = getDataRiskArea;
    $scope.getFamily = getFamily;

    $scope.sortBy = function (column) {
        $scope.sortColumn = column;
        $scope.reverse = !$scope.reverse;
    };
})
    .factory('familydataService', function ($http, $q, $timeout) {
        var service = {};
        var familyRiskAreaResult = [
            { FuId: 3, Area: 1, FamilyUnitName: 'Smiths' },
            { FuId: 1, Area: 1, FamilyUnitName: 'Jacksons' },
            { FuId: 4, Area: 1, FamilyUnitName: 'Monkey Jacksons' },
            { FuId: 2, Area: 1, FamilyUnitName: 'Stevens' },
            { FuId: 5, Area: 1, FamilyUnitName: 'Not unique also 3' }
        ];
        var familyUnit =
            [
                { Id: 1, FamilyUnitName: 'jks', Families: ['MJ', 'Janet', 'Latoia'] },
                { Id: 1, FamilyUnitName: 'jks', Families: ['Jacksons B1', 'Jacksons B2', 'Jacksons B3'] },
                { Id: 1, FamilyUnitName: 'jks', Families: ['Rebi', 'Dyana'] },
                { Id: 2, FamilyUnitName: 'Stvs', Families: ['Steven Seagal', 'Steven Tyler'] },
                { Id: 2, FamilyUnitName: 'Stvns', Families: ['King', 'Wonder'] },
                { Id: 3, FamilyUnitName: 'Smths', Families: ['MR', 'MRS'] }
            ];
        // Gets the Family by Risk Area ID
        service.getFamilysByRiskArea = function (id) {
            var deferred = $q.defer();
            //$timeout(function () {
                deferred.resolve(familyRiskAreaResult.filter(f => f.Area === id));
            //}, 1000);
            return deferred.promise;
        };

        // Get family by FuID
        service.getFamilyByFuId = function (id) {
            var deferred = $q.defer();
            //$timeout(function () {
            let res = familyUnit.filter(f => f.Id === id);
            let r = res.map(i => i.Families);
                deferred.resolve(r);
            //}, id * 1000);
            return deferred.promise;
        };
        return service;
    }).run(["$rootScope", run])    ;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<body ng-app="app">
    <div ng-controller="familyRiskCtrl">
        <div>
            <div style="display:inline-flex;"ng-repeat="b in [1,2,3]">
                <button ng-click="getDataRiskArea(b)">
                    <strong>Call Area {{b}}</strong>
                </button>
            </div>
        </div>
        <div>
            <h4>Area Loaded $refid</h4>
            <ul>
                <li ng-repeat="fam in familysR">
                    FuId: <strong>{{fam.FuId}} - {{fam.FamilyUnitName}}</strong>
                </li>
            </ul>
         </div>
        <div>
            <h4>Results</h4>
            <div ng-repeat="fu in FamilyUnit">
                <h5>{{fu.Id}} - {{fu.FamilyUnitName}}</h5>
                <div>
                    <span ng-if="fu.Families.length === 0">---No Families---</span>
                    <ul ng-if="fu.Families.length >0">
                        <li ng-repeat="f in fu.Families">{{f}}</li>
                    </ul>
                </div>
            </div>
        </div>
    </div>
</body>
  • Thanks for attempting a response. I still got same errors. Is it possible to delay the forEach Loop with a function? – Guzzyman Oct 29 '18 at 13:11
  • Please share your angular build version. ill create a working snippet for you. I 've been through a same grouping solution. – JVal aka SolidSnake Oct 29 '18 at 14:09
  • my angular build version is version 1.6.6 – Guzzyman Oct 29 '18 at 14:58
  • Please take a look. I hope this solves your problem. Let me know the results. If I am missing something on your logic or solution plz share. – JVal aka SolidSnake Oct 30 '18 at 05:47
  • @SolidSnake: Thanks for your response. So basically all I need are the function section of your code. The services are running pretty well or do I need to share my services for you to see? Secondly can we move this conversation to a private chat because it's becoming lengthy? – Guzzyman Oct 31 '18 at 10:56
  • try to use the controller and call `$scope.getDataRiskArea` when you want it to get **Area** results. I have no reputation to create a chat yet plz create and invite – JVal aka SolidSnake Oct 31 '18 at 11:59
  • @SolidSnake: How can I invite you to a chat? – Guzzyman Jun 07 '19 at 12:06