0

UPDATE

I am currently doing this, and I'm not sure why it works, but I don't think this is the correct approach. I might be abusing digest cycles, whatever those are.

First, I want to have the array navigationList be inside a service so I can pass it anywhere. That service will also update that array.

app.factory('ChapterService', [ 'ExtService', function(ExtService) {

  var navigationList = [];

  var getNavigation = function() {
    ExtService.getUrl('navigation.json').then(function(data) {
      angular.copy(data.navigationList, navigationList);
    });
  }
  return{
   getNavigation: getNavigation,
    navigationList: navigationList,
}
}]);

Then in my controller, I call the service to update the array, then I point the scope variable to it.

  ChapterService.getNavigation();
  $scope.navigationList = ChapterService.navigationList;
  console.log($scope.navigationList);

But this is where it gets weird. console.log returns an empty array [], BUT I have an ng-repeat in my html that uses $scope.navigationList, and it's correctly displaying the items in that array... I think this has something to do with digest cycles, but I'm not sure. Could anyone explain it to me and tell me if I'm approaching this the wrong way?


I have a main factory that runs functions and calculations. I am trying to run

app.factory('ChapterService', [ 'ExtService', function(ExtService) {

  var navigation = {
    getNavigationData : function () {
      ExtService.getUrl('navigation.json').then(function(data) {
        return data;
      });
    }
  }

  return: {
       navigation: navigation

}

I did a console.log on the data before it gets returned and it's the correct data, but for some reason, I can't return it.. The ExtService that has the getUrl method is just the one that's typically used (it returns a promise) In my controller, I want to do something like

$scope.navigation = ChapterService.navigation.getNavigationData();

in order to load the data from the file when the app initializes, but that doesn't work and when I run

console.log(ChapterService.navigation.getNavigationData());

I get null, but I don't know why. Should I use app.run() for something like this? I need this data to be loaded before anything else is done and I don't think I'm using the best approach...

EDIT

I'm not sure if I should do something similar to what's being done in this jsfiddle, the pattern is unfamiliar to me, so I'm not sure how to re purpose it for my needs

My code for ExtService is

app.factory('ExtService', function($http, $q, $compile) {
   return {
     getUrl: function(url) {
       var newurl = url + "?nocache=" + (new Date()).getTime();
       var deferred = $q.defer();
       $http.get(newurl, {cache: false})
        .success(function (data) {
            deferred.resolve(data);
        })
        .error(function (error) {
            deferred.reject(error);
        });

      return deferred.promise;
     }
   }
});

EDIT 2

I'd like to separate the request logic away from the controller, but at the same time, have it done when the app starts. I'd like the service function to just return the data, so I don't have to do further .then or .success on it...

2
  • Could you show me your code for ExtService? Commented May 19, 2015 at 9:38
  • @yuujin Yup, I added it. Commented May 19, 2015 at 9:44

3 Answers 3

1

You are using promises incorrectly. Think about what this means:

var navigation = {
    getNavigationData : function () {
        ExtService.getUrl('navigation.json').then(function(data) {
            return data;
        });
    }
}

getNavigationData is a function that doesn't return anything. When you're in the "then" clause, you're in a different function so return data only returns from the inner function. In fact, .then(function(data) { return data; }) is a no-op.

The important thing to understand about promises is that once you're in the promise paradigm, it's difficult to get out of it - your best bet is to stay inside it.

So first, return a promise from your function:

app.factory('ChapterService', [ 'ExtService', function(ExtService) {
    var navigation = {
        getNavigationData: function () {
            return ExtService.getUrl('navigation.json');
        }
    }

    return {
        navigation: navigation
    }
}])

Then use that promise in your controller:

app.controller('MyController', function($scope, ExtService) {
    ExtService
        .navigation
        .getNavigationData()
        .then(function(data) {
            $scope.navigation = data;
        });
})

Update

If you really want to avoid the promise paradigm, try the following, although I recommend thoroughly understanding the implications of this approach before doing so. The object you return from the service isn't immediately populated but once the call returns, Angular will complete a digest cycle and any scope bindings will be refreshed.

app.factory('ChapterService', [ 'ExtService', function(ExtService) {
    var navigation = {
        getNavigationData: function () {
            // create an object to return from the function
            var returnData = { }; 

            // set the properties of the object when the call has returned
            ExtService.getUrl('navigation.json')
                .then(function(x) { returnData.nav = x });

            // return the object - NB at this point in the function,
            // the .nav property has not been set
            return returnData;
        }
    }

    return {
        navigation: navigation
    }
}])

app.controller('MyController', function($scope, ExtService) {
    // assign $scope.navigation to the object returned
    // from the function - NB at this point the .nav property
    // has not been set, your bindings will need to refer to
    // $scope.navigation.nav
    $scope.navigation = ExtService
        .navigation
        .getNavigationData();
})
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks, I see. I am more than willing to restructure my code, I would just like to avoid using the promise in my controller. Is that possible? I feel like I'm currently using too many functions inside one another.
@thatandrey unfortunately that's just a consequence of asynchronous operations - they tend to spread over the whole codebase. You can either embrace it or look for a different solution. Angular's $resource does this by returning an object which has its properties assigned
Thanks, but when I wrote my example, I may have overcomplicated what I was trying to do. I updated my question to show you the progress I made (it works, but I don't know why).
@thatandrey your update works in a similar way to my solution. The crucial part is that you add a reference to an empty placeholder object (in your case an empty list), then populate it when the call finishes. The reason your console.log prints an empty array is that the call has not yet finished. Try adding a console.log statement just before the angular.copy operation - you will see that it occurs after your controller has finished setting up. For this reason you have to be careful about using the placeholder object before it has been initialized.
0

You are using a promise, so return that promise and use the resolve (.then) in the controller:

app.factory('ChapterService', [ 'ExtService', function(ExtService) {

    var navigation = {
        getNavigationData: function () {
            return ExtService.getUrl('navigation.json'); // returns a promise
        });
    }

    return: {
        navigation: navigation
    }
}

controller:

ChapterService
    .navigation
    .getNavigationData()
    .then(function (data) {
        // success
        $scope.navigation = data;
    }, function (response) {
        // fail
    });

5 Comments

Thanks, I tried that but it didn't work. I added my ExtService code because I think it's already doing what you're doing, so I may have incorrectly explained what I had been doing.
This should work with that ExtService code. Try console.log(data) in the success callback and see what it outputs
Ok, I will, but it still seems like a lot of code... Can I restructure what I'm attempting to something similar to the fiddle I linked to?
Ok, doing console.log(data) works, but doing console.log($scope.navigation) outside of that block of code returns null..
I'd like to separate the request from the controller, is there anyway to be able to write something like this in my controller: $scope.data=getData();
0

This is a different approach, I don't know what your data looks like so I am not able to test it for you.

Controller

.controller('homeCtrl', function($scope, $routeParams, ChapterService) {
    ChapterService.getNavigationData();
})

Factory

.factory('ChapterService', [ 'ExtService', function(ExtService)  {

        function makeRequest(response) {
            return ExtService.getUrl('navigation.json')
        }

        function parseResponse(response) {
            retries = 0;
            if (!response) {
                return false;
            }
            return response.data;
        }

        var navigation =  {
            getNavigationData: function () {
                return makeRequest() 
                    .then(parseResponse)
                    .catch(function(err){
                        console.log(err);
                    });
            }
        }

        return navigation;


    }])

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.