0

I'm still getting my head around AngularJS, so my question may be flawed, but is it possible to delay the execution of a controller until the json files are loaded (in a different function)?
Here's the controller:

app.controller('progressCtrl', ['$scope', '$routeParams', '$http', 'content', function($scope, $routeParams, $http, content) {

    function initScope() {
        $scope.pageId = $routeParams.pageId; 
        $scope.totalPages = 0;

        content.success(function(data) {
        $scope.pages = data;
        angular.forEach($scope.pages, function(value, key) {
            if ($scope.totalPages <= value.id) $scope.totalPages = value.id;
            if ($scope.pageId == value.id) {
                value.class = "active";
            } else {
                value.class = "";
            }
            if (incompleteObjectives.length>0) {
                var matchFound = false;
                for (var i=0;i<incompleteObjectives.length-1;i++) {
                    if (incompleteObjectives[i].indexOf('page-'+value.id+'-')) {
                        matchFound = true;
                    }
                }
                if (!matchFound) {
                    value.class += " completed";
                }
            } else {
                value.class += " completed";
            }
        });
    });
}

    initScope();

}]);

And here is the factory that calls the 'getData' function, which is where the json files are loaded...

/*Factory that runs http.get on content.json, used by multiple controllers */
app.factory('content', ['$http', function($http) {
    var url = 'json/content.js';
    return $http.get(url)
        .success(function(data) {

            for (var i = 0; i < data.length; i++) {
                numberOfPages += 1;
            }
            // load page .js files
            var tempData = getData(0);
            for (var i = 1; i < numberOfPages; i++) {
                (function(i) {
                    tempData = tempData.then(function() {
                        return getData(i);
                    });
                }(i));
            }

            return data;
        })
        .error(function(data) {
            return data;
        });
}]);


// loads individual page data, stores in pageData
function getData(id) {
    return $.ajax({
        url: 'json/page' + (id + 1) + '.js',
        dataType: 'json'
    }).done(function(d) {
        if(firstRun)
            addPageObjectives(id, d)
        // push data into pageData array, for use later when pages are routed to
        console.log("pushing page data into pageData for page "+id);
        pageData.push(d);
    }).fail(function() {
        // console.log('ERROR loading page index ' + id);
    });
}

For completion's sake, here's the angular html, which features an ng-class:

<div ng-if="content.titletext!=undefined" class="sidetitle" ng-controller="progressCtrl">
                <div class="container">
                    <div>
                        {{content.titletext}}
                    </div>
                    <div class="progress-buttons">
                        <div ng-repeat="(skey,page) in pages" class="button {{skey+1}}" ng-class="page.class" ng-attr-id="{{'nav-'+(skey+1)}}">
                            <div ng-switch="page.titletext || '_undefined_'">
                                <span ng-switch-when="_undefined_">{{skey+1}}</span>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="container">
                    <div ng-if="content.chapterTitleText!=undefined" class="side-chapter-title">
                        {{content.chapterTitleText}}
                    </div>
                    <div class="progress-pages">
                        <span>{{pageId}} of {{totalPages}}</span>
                    </div>
                </div>
            </div>

Ultimately it's the pageData array I want to access but, at the point the progress controller is running only the first json file has loaded so I can only access details from the first page. I'd like to have the code in progressController run when all the json files are loaded - and still have access to the value.class property.

Is that possible?
Thanks.

UPDATE
From an answer below, here's what I've tried. I know the following is wrong, but I'd like some tips on how it's wrong, if possible.

/*Factory that runs http.get on content.json, used by multiple controllers */
app.factory('content', ['$http', function($http) {
    var promise;
    var url = 'json/content.js';
    function init() {
        console.log("Good news! content factory init function does get called!");
        promise = $http.get(url).then(function success(data) {
            for (var i = 0; i < data.length; i++) {
                numberOfPages += 1;
            }
            console.log("data.length = "+data.length); // this outputs as 'undefined'; I'm not sure why, given loading was, presumably, successful
            // load page .js files
            var tempData = getData(0);
            for (var i = 1; i < numberOfPages; i++) {
                (function(i) {
                    tempData = tempData.then(function() {
                        return getData(i);
                    });
                }(i));
            }
            return data;
        }); // I took off the .error lines because they threw an error - I'm assuming the syntax didn't match the new 'success' syntax above
    }

    init(); // I added this - not sure if that's right, but otherwise the init function didn't run...

    function getData(id) {
        return $.ajax({
            url: 'json/page' + (id + 1) + '.js',
            dataType: 'json'
        }).done(function(d) {
            if(firstRun)
                addPageObjectives(id, d)
            // push data into pageData array, for use later when pages are routed to
            console.log("pushing page data into pageData for page "+id);
            pageData.push(d);
        }).fail(function() {
            // console.log('ERROR loading page index ' + id);
        });
        return promise;
    }

    // public API
    return {
        getData: getData
    }

}]);

Elsewhere, in the controllers I'm using lines, such as:

content.getData().then(function(data) {
    ....
}

...to access the page data (which, I think is not working). I'm guessing this update is wrong in a lot of places, but if folks are willing to help, that would be great. Thanks again.

moosefetcher
  • 1,841
  • 2
  • 23
  • 39
  • 1
    (using `$http` **and** `$.ajax` is just rude) – Aleksey Solovey Dec 04 '17 at 14:21
  • 1
    Unsurprisingly, I do NOT know what you mean at all. I assume it's some kind of angular joke. If so - good one! I get it! (To be clear, I don't get it). – moosefetcher Dec 04 '17 at 14:25
  • I think they're saying that it's odd that you're using both methods rather than choosing just one – Matt Fletcher Dec 04 '17 at 14:29
  • There's a team of us working on this, and I've just come back to this project (is that an effective dodge?). I'm also still an angular newbie, so I have no appreciation for why it matters that both methods are used. If anyone has an answer on the (I assume) bigger issue, I'd be keen to hear it. – moosefetcher Dec 04 '17 at 14:32
  • @moosefetcher using angularjs' $http will trigger a scope digest – JusMalcolm Dec 04 '17 at 14:37
  • OK... I'll keep that in mind. At some point it might even be meaningful to me. I suspect I probably shouldn't be working with angular. Or are you all just making stuff up? 'Hey look what I got the new guy to believe!'. Hilarious! You guys... – moosefetcher Dec 04 '17 at 14:48
  • Learn how to use `$q.all()` to resolve an array of promises and get rid of using `$.ajax` – charlietfl Dec 04 '17 at 14:51

2 Answers2

0

To load the data before the page even appears can be done with resolve in your config.

Simply transform your factory/service into a resolver by adding a function and calling it in your config.

$routeProvider
.when("/", {
    templateUrl : "...",
    controller : "...",
    resolve : {
        resolved_data: function resolveData($routeParams) {
            return content.resolveData($routeParams.pageId);
        }
    }
})

Then your controller can just take the results from it with:

app.controller('progressCtrl', ['resolved_data', ... function( resolved_data, ... ){
    $scope.some_content = resolved_data.data; 
    ...
}])'

Since it returns data from $http, the controller should deal with a promise:

app.controller('progressCtrl', ['resolved_data', ... function( resolved_data, ... ){
    resolved_data.then((res)=>{
        $scope.some_content = res.data; 
    });
    ...
}])'
Aleksey Solovey
  • 4,153
  • 3
  • 15
  • 34
  • Oh god. Every time I try to learn something about angular there's a new piece of jargon that needs a whole back-story of explanation. But thanks for your answer. Instead of pestering you for more layman-styled details I'll do some research on your answer and get back to you in several months, I suspect. (Unless you think I can just copy and paste your answer and it will magically work. No. I expect not). But honestly, thanks again! – moosefetcher Dec 04 '17 at 14:40
  • It might work if you wrap everything in "content" factory into one function. Try to initialise it as `var service = {};`, appending functions into that with `service.resolveData = function(pageId){...}` syntax, and returning the factory with `return service;`. The rest of my code should make it work all together – Aleksey Solovey Dec 04 '17 at 14:45
  • This doesn't help OP return the promise needed to even do the resolve – charlietfl Dec 04 '17 at 14:49
0

My suggestion would be to expose a getData function in your contents factory that returns a Promise with the data results. You can use the $q service to aggregate your pageData http calls.

app.factory('content', ['$http', '$q', function($http, $q) {
var promise;
var url = 'json/content.js';
function init() {
    promise = $http.get(url).then(function success(data) {
        var numberOfPages = 0;
        var dataRequests = [];
        for (var i = 0; i < data.length; i++) {
            numberOfPages += 1;
        }
        // load page .js files
        for (var i = 0; i < numberOfPages; i++) {
            dataRequests.push(fetchPageData(i));
        }

        return $q.all(dataRequests).then(function (pageData) {
            /* pageData is an array of results from fetchPageData calls */
            return pageData;
        });
    });
}

function fetchPageData(id) {
    return $http({
        method: 'GET',
        url: 'json/page' + (id + 1) + '.js',
        responseType: 'json'
    }).then(function(d) {
        return d;
    }, function(error) {/*handle error*/});
}

function getData () {
    return promise; /* this is the pageData result from $q.all above */
}

init();

// public API
return {
    getData: getData
}
...

in your controller:

content.getData().then(function(data) {
    //do stuff here
})
JusMalcolm
  • 1,431
  • 12
  • 10
  • Using `$q.defer` with `$http` is an anti-pattern when `$http` already returns a promise. [What is the explicit promise construction antipattern and how do I avoid it?](https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it) – charlietfl Dec 04 '17 at 15:07
  • Thanks for your answer. I so want to understand how this might work, but I don't think there's enough here to have me guess the rest. I have too many questions about where the current code should go and how it would need to be altered. But when I read angular documentation or tutorials, they all seem too abstract to see how they're actually applied. I don't think I have my head around the abstractions to begin with. Oh well. – moosefetcher Dec 04 '17 at 15:08
  • @charlietfl yeah i thought it would be easier to write but i'll edit to return the $http instead – JusMalcolm Dec 04 '17 at 15:42
  • @moosefetcher just fill in the `...http calls & data processing` part with what you need to get the correct data that your controllers need - getting the pages and loading page data. When that processing is done, return the results within the `$http` success function and your controllers injecting the 'content' factory can access them using `getData()`. – JusMalcolm Dec 04 '17 at 15:50
  • @JusMalcolm I've tried implementing what you suggest. I suspect the reason it's not working is that I've not done anything with the code in my original 'getData' function. Should that go in the new 'getData' function in the content factory? And, if so, what do I do with the 'return $.ajax' part? How can I include the code to iterate through the pages AND return 'promise'. If you have any suggestions you will save me another day of pain. Thanks. – moosefetcher Dec 05 '17 at 08:45
  • @JusMalcolm I've tried just copying the whole getData function (including the 'return $.ajax' line) into the new getData function and it seems to work. Some other things are broken; I'm not sure if I shouldn't include that ajax line, but I'm clueless about the syntax for an alternative. Any help, much appreciated. – moosefetcher Dec 05 '17 at 08:50
  • @moosefetcher i'll take a look at what you've written and add some comments – JusMalcolm Dec 05 '17 at 16:43
  • @moosefetcher I've edited to provide a more specific example – JusMalcolm Dec 05 '17 at 17:11
  • @Malcolm Thanks for all your help. I'm still getting the 'data' parameter console.logging as 'undefined'. The application is only recognising one page, when there are three. I think, perhaps, it is time for me to give up. – moosefetcher Dec 06 '17 at 08:49
  • @moosefetcher if you set up a codepen with your example, I might be able to help more – JusMalcolm Dec 06 '17 at 16:32