3

I have an Angular app with a controller that accesses a service. I have the service properly sending data back to my controller, but in the .then function I can't seem to access specific elements of the json data.

Controller (snippet)

    vm.getData = function(eventid) {
        monitorData.requestEvent(eventid)
            .then(function(data) {
                // console.log(data);
                vm.event.id = data.id;
            })
            .catch(function(e) {
                console.log("Error");
            });
        return false;
    };

    vm.getData($routeParams.eventid);

Sample Data

{
    id: "123456",
    type: "pokemon",
    time: "July 13, 2016 at 04:00 PM Eastern Daylight Time",
    oneURL: "www.junk1.com",
    twoURL: "www.junk2.com",
    threeURL: "www.junk3.com",
    phone: "1 (555) 765-4321",
    notes: "some junk here",
}

if I do console.log(data); it prints into my browser console. But I tried setting a variable equal to data then printing it outside the function, and it won't. Nor can I access any specific parts of the json object.

I'm sure there is a simple explanation for this, but I can't seem to find it anywhere.

...Edit (adding the service code)

(function() {

    angular
        .module('monitorApp')
        .service('monitorData', monitorData);

    monitorData.$inject = ['$http'];
    function monitorData($http) {
        var requestEvent = function(eventid) {
            return $http.get('/api/event/' + eventid);
        };

        return {
            requestEvent : requestEvent,
        };
    }

})();

...Edit for clarification

Because of previous confusion by a user, let me try to be more clear: In my controller code above you'll see

.then(function(data) {
    // console.log(data);
    vm.event.id = data.id;
})

Inside of this snippet, you'll see data. That data contains the object I have listed above (Sample Data).

My question is, is there some way to further access id, type, time, ... phone, notes. I can console.log(data) and it will print the object out in the console (for testing purposes), but I need to access each item in the object and use it outside the entire vm.getData function.

Someone previously said "it's not possible", would others agree?

Kenny
  • 2,124
  • 3
  • 33
  • 63
  • What's the error? Any messages in your console? – TimoStaudinger Jul 13 '16 at 20:29
  • No errors at all. if I do `vm.event.id = data` I get the entire object. If I do `vm.event.id = data.id` I get nothing, no error, nothing of the object, nothing. – Kenny Jul 13 '16 at 20:30
  • *"I'm sure there is a simple explanation for this"* - Asynchronousness. – dfsq Jul 13 '16 at 20:31
  • Thanks @dfsq for the point in a nice direction, but the new question is more like a lecture on best practices, I don't see anything specific that I can apply to my situation. Will you open my question back up or help me figure out, with code, how I can read the `data` from the promise? – Kenny Jul 13 '16 at 20:42
  • Did you read the answer in the duplicated question? There is section "Use promises". Exactly what you need to do. – dfsq Jul 13 '16 at 20:46
  • Well, I see they're doing what I'm already doing in my then/catch block, they're doing a simple `console.log(data)` which does print the data. But I need to access the object, and it does not contain an example for that. – Kenny Jul 13 '16 at 20:49
  • Okay, maybe you didn't read carefully: you **can't do it**, it's not possible. That's the point of having `then` callbacks - you subscribe to data and use it inside callback. You may think you need data outside of it, but believe me you don't. What do you want to do with data outside? I can suggest you proper async pattern for your situation. – dfsq Jul 13 '16 at 20:50
  • Are you using `$resource`? – Muli Yulzary Jul 13 '16 at 21:03
  • @MuliYulzary if you're talking about my service, I use `return $q.resolve($http.get('...'));` I'll add the service to my question above. – Kenny Jul 13 '16 at 21:04
  • There's no need to use `$q.resolve($http.get(...))`. `$http.get` already returns a promise. – Muli Yulzary Jul 13 '16 at 21:05
  • Why not call a function within the callback that is outside of the asynchronous request? Then do whatever you want to do with the data there. See this post: http://stackoverflow.com/questions/33981636/angularjs-how-to-pass-data-outside-a-succes-call-in-http-get – A.Sharma Jul 13 '16 at 21:08
  • possible duplicate? http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call/14220323#14220323 – Kevin B Jul 13 '16 at 21:10
  • @A.Sharma I've tried `function handleData(data) { console.log(data.id); }` but it still won't let me access the specific values of the json object. Any idea how to access them? @KevinB thanks, I'm seen that thread; the only conclusion from that was that 'it's not possible'. – Kenny Jul 13 '16 at 21:12
  • then why are you still trying? You *must* use some form of a callback. there is no magic method that will let you break away from that. – Kevin B Jul 13 '16 at 21:13
  • Because there's a way to get json object from an api and that's what I've done, now I need to decouple the object. I know this is possible, I'm trying to figure out how. I've only learned deprecated methods from older Angular versions. – Kenny Jul 13 '16 at 21:15
  • You could go down a brute force method and store it into localStorage – A.Sharma Jul 13 '16 at 21:16

2 Answers2

0

From what I can understand this controller is bound to a specific resource view (details of some event).

Here are 2 ways to achieve your desire:

Use resolve clause in state definition (I think this is more elegant):

//Note: this is assuming ui-router, but also possible in ng-route.
.state('event.details', {
 url: '/event/:id',
 controller: ...
 templateUrl: ...
 resolve: {
  event: function($stateParams, $state, monitorData){
   //Will wait for this GET request to finish before loading the view
   //You can then inject event into the controller as any service
   //like so: .controller('eventCtrl', function(event){}...)
   return monitorData.requestEvent($stateParams.id)
          .catch(function(e){
           //Something went wrong, redirect the user and notify them
           $state.go('error', {error: e});
          });
   }
 }
});

Cache the result of the promise:

var promise;
vm.getData = function(eventid) {
        promise = monitorData.requestEvent(eventid)
            .then(function(data) {
                // console.log(data);
                vm.event.id = data.id;
            })
            .catch(function(e) {
                console.log("Error");
            });
        return false;
    };

//Wherever you need the results of the promise just use the cached version
//Note: this might fail if you depend on this promise when your controller first loads.
promise.then(function(data){
 //data.id available here
});
Muli Yulzary
  • 2,559
  • 3
  • 21
  • 39
  • Thanks so much for taking the time to answer and help me with this. This is very close I think. I'm using ng-route in my app.js and the route is `.when('/event/:eventid', { templateUrl: 'bundles/app_client/event/event.view.html', controller: 'eventCtrl', controllerAs: 'vm' })`. So I like the first approach, would I simply alter my current route or do I need to change `.when` to `.state`? – Kenny Jul 13 '16 at 21:30
  • No you can alter your current route. `.state` is for `ui-router` as I specified. – Muli Yulzary Jul 13 '16 at 21:31
  • If using ng-route, would I still use `$stateParams and $state`? Or there an additional library that I'll need? It just says unknown provider. – Kenny Jul 13 '16 at 21:35
0

The solution to this was in the service. The problem was that I was returning the following:

(WRONG)

return $http.get('/api/event/' + eventid);

So I instead tacked on a .then function to chain promises. I was also returning data when I should have been returning response.data. See the code below:

(RIGHT)

return $http.get('/api/event/' + eventid).then(function(response) {
    return response.data;
});

The final Service:

(function() {

    angular
        .module('monitorApp')
        .service('monitorData', monitorData);

    monitorData.$inject = ['$http'];
    function monitorData($http) {
        var requestEvent = function(eventid) {
            return $http.get('/api/event/' + eventid).then(function(response) {
                return response.data;
            });
        };

        return {
            requestEvent : requestEvent,
        };
    }

})();

The final Controller

vm.getData = function(eventid) {
    monitorData.requestEvent(eventid)
        .then(function(data) {
            console.log(data.id);
        })
        .catch(function(e) {
            console.log("Error");
        });
    return false;
};

vm.getData($routeParams.eventid);

Conclusion

So now I can successfully make an api call in my controller by using a service and a chain of promises. The solution arrived when I added .then to the return statement returning return response.data;. I presume if the api call takes forever, this should still work. I could easily add a waiting indicator in my controller code before the monitorData.requestEvent(eventid), then when the .then statement is activated I could remove the waiting indicator.

Kenny
  • 2,124
  • 3
  • 33
  • 63