I'm relatively new to Angular and trying to correct some functionality someone left us with. I have a service that retrieves JSON from another service, and then I'd like to use that JSON within the original service. I have it working when accessing a single value from the JSON being returned, but I'd like to store the entire object so I can easily reference it throughout.
For instance, this works, and sets the value of title (title: cfgTitle) to the TITLE value coming back from the JSON:
.service("rtmModel", function($q, dataGetter, $http) {
this.getModel = function() {
var cfgProm = dataGetter.getData()
var cfgTitle = cfgProm.then(function(response) {
return response.data.record1.TITLE;
});
return {
title: cfgTitle,
desc:
...
However, if I return anything else from the "then" function I can't get stuff to show up. For instance, this does not work (appears to be passing undefined for the title param):
.service("rtmModel", function($q, dataGetter, $http) {
this.getModel = function() {
var cfgProm = dataGetter.getData()
var cfgTitle = cfgProm.then(function(response) {
return response.data.record1;
});
return {
title: cfgTitle.TITLE,
desc:
...
If I simply return "response" from the "then" function and do:
return {
title: cfgTitle.
then I get the entire JSON string passed into Title (and I understand why), but attempting to drill down (i.e. title: cfgTitle.data, or title: cfgTitle.data.record1) just results in undefined values.
I've tried various methods of storing the returning JSON data, but can't find the secret sauce that will allow me to store the JSON object, and then use it to pass multiple parameters down below (i.e. I want to pass a value into title, another into desc, and so forth).
Any pointers would be appreciated.
You can't return values/objects from the anonymous function callbacks that you pass to the .then method of a promise. Those callbacks are invoked internally by the promise library and the return value is ignored. You have two options. Refer a reference to a new object from getModel and copy the data returns from getData into it. Or, return a new promise from your getModel method, and resolve that promise when the promise returned from getData is resolved.
The later option (returning a promise to the caller) will look something like this (won't compile):
.service("rtmModel", function($q, dataGetter, $http) {
this.getModel = function() {
var defer = $q.defer();
dataGetter.getData().then(function(response) {
defer.resolve(response.data.record1);
});
return defer.promise;
Related
I have the following code in a controller:
$scope.chart = $resource('/api/chart/01234').get();
// { name: 'Foobar', id: '01234' }
$scope.send = function() {
$scope.chart.$save();
}
But after the user triggers send(), the only properties remaining in $scope.chart are those from $resource (e.g. $promise, $save, toJSON, etc…), the others are gone (no name or id).
I still don't understand why the instance's $save() wipes the instance, but using the class' save() works:
var Chart = $resource('/api/chart/01234');
$scope.chart = Chart.get();
$scope.send = function() {
Chart.save({}, $scope.chart);
}
Could be this:
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data.
From angular docs under the Returns section.
I'm trying to get the new Firebase queries working in my factory, but I'm stuck. I want to return the value of 'logo' out of my opponents table, based on the opponent name. I can get the example with console.log from the Firebase docs running:
function OpponentService($firebase) {
var factory = {};
var _url = 'https://somefirebaseproject.firebaseio.com/opponents';
var _ref = new Firebase(_url);
factory.getLogo = function(opponent) {
_ref.orderByChild('name').equalTo(opponent).on("child_added", function(snapshot){
console.log(snapshot.val().logo);
});
};
return factory;
}
When I call my factory with OpponentService.getLogo(opponentName) in my controller, I get the correct logo id in my console. But how can I return the value instead of sending it to my console? I want to store it in a variable like this: $scope.opponentLogo = OpponentService.getLogo(opponentName). I tried several variations with a return statement like this:
factory.getLogo = function(opponent) {
_ref.orderByChild('name').equalTo(opponent).on("child_added", function(snapshot){
return snapshot.val().logo;
});
};
But apparently I don't fully understand how factories work in Angular, because I get an undefined. Could it be that the value isn't available yet and I should use a promise in someway? Anyone who can point me in the right direction?
You're returning the value of logo from the anonymous function inside the Firebase on() call, but you're not returning anything from getLogo().
Returning a promise would be a good way to do this. This is how you retrieve the opponent logo with AngularFire, if there is no guarantee opponentName will be unique:
// getLogo() returns a promise that you can bind to the UI.
// When loading finishes, the binding will get the opponent's logo value.
factory.getLogo = function (opponentName) {
var opponentsArray = $firebase(_ref.orderByChild('name').equalTo(opponentName)).$asArray();
// $loaded() returns a promise, so we can use the then() method to add
// functionality when the array finishes loading and the promise resolves.
var r = opponentsArray.$loaded().then(function () {
// Now return the logo for the first matching opponent
return opponentsArray[0].logo;
});
return r;
};
If opponentName is unique, I would rethink the data structure so that you could use the opponentName as the key for opponents. Then you would be guaranteed to get a single opponent and could fetch it with:
var opponent = $firebase(_ref.child(opponentName)).$asObject();
If you're not using AngularFire, you can return a promise using Angular's $q service. The following should work:
factory.getLogo = function(opponent) {
$q(function (resolve, reject) {
function successCallback(snapshot) {
resolve(snapshot.val().logo);
};
function cancelCallback(error) {
reject(error); // pass along the error object
};
_ref.orderByChild('name').equalTo(opponent)
.on("child_added", successCallback, cancelCallback);
});
};
You can assign the result of getLogo() to a scope variable, and bindings will update in the UI when Firebase returns the value.
UPDATE 2:
I found out where the problem was coming from and I left out the important part.
I'm setting the property in the first controller INSIDE this
$rootScope.$on('anEvent', function (event, data) {
InjectedService.setToken(data.value);
})
But I can't grab it from outside that callback scope. The event that is listened for depends on an $http request so I'm guessing that is the problem. Are there any workarounds to this?
UPDATE:
The order of controllers are the other way around so that the code in secondController is actually being called first in my actual app. I have reordered them accordingly
Question:
I'm trying to grab the services specific property but when I try to grab the service property, I get undefined (using a getter function or not). But when I try to grab the full service, I get everything with the correct property and value.
main.js
angular.module('myApp', ['myModule', 'myServices'])
.controller('firstController', ['InjectedService', function(InjectedService) {
InjectedService.setProperty('Hello World')
}])
othermodule.js:
angular.module('myModule', [])
.controller('secondController', ['InjectedService',
function (InjectedService) {
console.log(InjectedService); // Full object with property == 'hello world'
console.log(InjectedService.property); // I get undefined
console.log(InjectedService.getProperty()); // I get undefined
// interesting to note:
InjectedService.setToken('A different string');
console.log(InjectedService.property); // I get 'A different string'
console.log(InjectedService); // Full object with property == 'Hello World' which was set in second controller
}])
services.js:
angular.module('myServices', function
.service('InjectedService', function(){
var Service = this;
Service.setProperty = function (value) {
Service.property = value;
}
Service.getProperty = function () {
return Service.property;
}
Service.unsetProperty = function () {
Service.property = null;
}
return Service;
})
It seems to me a scope problem, but the variable isn't a primitive type. Any suggestions?
I asked the wrong question yesterday (and got a goodanswer that worked), but am realizing it's not what I needed. I need to be able to retrieve JSON data (preferably once), store it, and access it throughout my service. The challenge I'm having is that all the examples I can find talk about using JSON and passing to the app/controller, whereas in this case I need to get it, check it, and then it dictates what my module/service does.
For instance, I have my App and Controller, and then I have a module such as (this is psuedo-code, not meant to run):
angular.module("myModule")
.service("myService1", function($q, myService2, $http) {
this.getModel = function() {
return {
title: "My Title",
desc: "My Desc"
options: function () {
if (condition A)
return "option1";
else
return "option2";
}
};
};
})
.service("myService2", function($q, $http) {
this.getCfgInfo = function () {
var defer = $q.defer();
$http.get("my/json/url").then(function(response) {
defer.resolve(response.data);
});
return defer.promise;
};
})
In this example, I'm wanting to get the JSON, and use it within myService1 for both literal values (title, desc) as well as for conditions (condition A within the if).
I know I can do something like this (thanks to Joel for helping yesterday):
service("myService1", function($q, myService2, $http) {
// get a promise object for the configuration info
var cfgProm = rtDataMapper.getCfgInfo()
this.getModel = function() {
return {
title: cfgProm.then(function(response) {
return response.JSON_NAME;
}),
and it works fine as I've got the title mapped back into my model and there is a watch(), but I'm stumped as to how I get, store, and use the JSON within the service itself as a conditional (i.e. if (condition A) where condition A is coming from the JSON. Trying to wrap these in .then() doesn't seem to make sense, or at least I can't figure out how to do it.
I'm new to Angular and am attempting to modify some code that was left to us. I'm guessing I don't need the myService2 just to get the JSON. Can anyone help point me in the right direction? I've spent several hours online but can't seem to find a relevant reference/example.
Thanks
Live demo (click).
I'm having the service immediately get the data when it is injected (that code will only run once no matter how many times you inject it). That's nice because you won't have to call a function to get the data - it's called for when creating the service.
Your service method that returns that data will need to return the promise of the data, of course, since you aren't guaranteed that it will have come through when you ask for it. You can pass arguments to that method to use to determine your conditions. All you need to do for that is use promise.then in the method and resolve the promise with the modified data. Since that method is returning the promise already, the modification will be updated on the resolve. See all of this below and in the demo.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
myService.getData(15).then(function(data) {
$scope.myData = data;
});
});
app.factory('myService', function($q, $timeout) {
//this code only runs once when you first inject the service
//get data immediately
var deferred = $q.defer();
$timeout(function() { //simulate ajax call
var data = { //ajax response data
foo: 15,
bar: 'Some data!'
};
data = modifyData(data, 1);
deferred.resolve(data);
}, 500);
function modifyData(data, fooVal) {
if (data.foo === fooVal) {
data.baz = 'Conditional data!';
}
return data;
}
var myService = {
//data can be modified when it comes from the server,
//or any time you call this function
getData: function(fooVal) {
if (fooVal) { //if you want to modify the data
deferred.promise.then(function(data) {
data = modifyData(data, fooVal);
deferred.resolve(data);
});
}
return deferred.promise;
}
};
return myService;
});
I have a resource factory with a POST method called update:
PnrApp.factory('Feed', function ($resource, $cacheFactory, $q, $rootScope) {
var Feed = $resource('api/feeds/:post', { post: 'post' }, {
get: { method:'GET' },
update: { method: 'POST' }
});
return Feed;
});
When I call the method it POSTs the data to the server as expected:
$rootScope.toggleStar = function (post, feedname) {
var updated = Feed.update(post);
this.child.StarId = updated.StarId;
}
And the server returns the correct values (notice the StarId in this json):
{"Name":"13 Ways to Act Like A Business Owner","ItemDate":"June 6, 2013","Url":"/post/13-Ways-to-Act-Like-A-Business-Owner-Stop-Acting-Like-an-Advisor-All-the-Time-(6-min-03-sec).aspx","StarImg":"bulletstar-on.png","StarId":1324,"StarDate":"0001-01-01T00:00:00","FeedCount":0,"FeedId":19,"SourceIcon":null,"IsBroken":false,"ItemId":"01"}
However, if you look at var updated's return value for StarId, notice how it's "0":
Can someone explain why this is, and how I can get at the return values in this situation?
Your var updated = Feed.update(post); makes an async call to the server and returns immedaitly and the updated object gets updated as soon as the server returns the data. So I guess you try to access the updated.StarId too early. From the angular doc:
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. Having an empty object results in no rendering, once the data arrives from the server then the object is populated with the data and the view automatically re-renders itself showing the new data. This means ththeat in most case one never has to write a callback function for the action methods.
Try something like this:
$rootScope.toggleStar = function (post, feedname) {
var updated = Feed.update(post, function(f) {
this.child.StarId = f.StarId;
});
}