I have a simple factory below that request a json object with some location info. The requests works, I can see that the data is in the object. But somehow there is scope problem. I cannot access the properties of the locations object, even if I know that the object has the property and the correct array.
You can see in the code snippet below (if you look at the comments) that I cannot access some value of the object when trying to ouput to the console.
Any idea on what could be the problem?
.factory('Spots', function(){
return{
all: function($resource){
var locations;
var Locations = $resource('http://localhost\\:3000/locationsd/');
locations = Locations.get(function(){
console.log(locations.results[0].obj.name); // THIS WORKS -> gives me the name of the location
});
console.log(locations); // THIS WORKS -> log an object with a result array, etc.
console.log(locations.results[0].obj.name); // THIS DOESNT WORK -> TypeError: Cannot read property '0' of undefined
return locations;
}
}
})
This is due to it being async. To utilize $resource properly within a service, you should use promises.
.factory('Spots', function(){
return{
all: function($resource){
var Locations = $resource('http://localhost\\:3000/locationsd/');
return Locations.get().$promise.then(function(res){
return res;
});
}
}
})
Then when calling the function from your controller:
Spots.all().then(function(res){
//do stuff with res here
})
If you aren't aware of what promises are, read about them here: https://docs.angularjs.org/api/ng/service/$q
General idea is the resource promises that it will finish eventually, and when it's done, it'll call the function you pass into its then().
Related
I'm calling a local service that calls a remote service and returns an object like this back:
but If I try to set my scope variable to it, it keeps saying undefined? Here are the two different ways I've tried.
themefactory.then(function (data) {
$scope.themeObject = data.theme;
$scope.themeObj = angular.fromJson(data.theme);
};
What am I doing wrong?
themefactory.then(function (data) {
$scope.themeObject = data.Data.theme;
};
I'm currently having an issue writing some tests for a controller. Within the beforeEach block below I need to instantiate an activityController and inject the scope object. I have added a console log before the call to the $controller service and this is outputted however the one after never gets called therefore something is breaking within the $controller block.
beforeEach(inject(function($controller) {
console.log(activityController);
activityController = $controller('activityController', {
'$scope': $scope
});
console.log("TEST");
}));
Within my tests I'm seeing Type Error: activityController is undefined in C:\.......\activity.controller.test.js so I know it's definitely not being instantiated.
I've created a gist of the relevant files here: https://gist.github.com/junderhill/e181ce866ab1ebb1f805
The activity controller not being instantiated correctly is causing my tests to fail. Any ideas on what may be causing this would be appreciated. Thanks
Jason.
Try to set activityService whilst creating controller, because you should also inject all services.
Looks like this line may be causing the problem:
mockRoleService.setCurrentRole({"AssignmentID":21,"EndDate":"2049-12-31T00:00:00","StartDate":"2000-01-01T00:00:00","UserType":1,"AccessLevel":"00000000-0000-0000-0000-000000000000","Description":"Demonstration Territory 1","TeamID":null});
It looks like you're using an actual injected version of your roleService instead of a stubbed literal, so it's actually going to fire off your implementation, which is...
this.setCurrentRole = function(role){
currentRole = role;
$http.get("http://localhost:14938/api/User/GetTeamForAssignment?assignmentId=" + role["AssignmentID"] + "&assignmentType=" + role["UserType"])
.success(function (data) {
currentTeam = data;
});
}
If you're going to use that service directly with an $httpBackend mock, I'd actually wrap that operation in a $q.defer(), because currently as that stands, that is an asychronous call. You'll want that operation to complete to set the currentTeam properly. So, maybe something like..
this.setCurrentRole = function(role){
var deferred = $q.defer();
currentRole = role;
$http.get("http://localhost:14938/api/User/GetTeamForAssignment?assignmentId=" + role["AssignmentID"] + "&assignmentType=" + role["UserType"])
.success(function (data) {
currentTeam = data;
deferred.resolve();
});
return deferred.promise;
}
And obviously do a deferred.reject of some sort if something wonky comes back from HTTP.
Hope that helps!
Eric
I am working with a mobile App which uses Parse as a backend and I have an issue with the find function. When running the find function in the format of:
var = firstQuery = (new Parse.Query("MyParseObject"))
.find(),
secondQuery = (new Parse.Query("OtherParseObject")).get(id)
// there is only one object that firstQuery can find
Parse.Promise.when(firstQuery, secondQuery)
.then( function (query1res, query2res) {
// query1res should return only one result wrapped in an array,
// instead query1res is an object without a get method
query1res.forEach (function (res) {
// this fails: cannot get .length of undefined
})
// ... do stuff with the returned data
})
Is there something i am missing? I am sure this used to work before, but now it does not.
It is quite difficult to correctly get in an debug this issue thanks to the way Parse works, but their docs outline that this should return an array, but it does not at this point of time.
Thanks for your help.
Based on the Parse docs, it looks like Parse.Promise.when expects an array, although based on this support thread, the results will be passed in as individual arguments to the then handler. Give this a try:
Parse.Promise.when([firstQuery, secondQuery])
.then(function (query1res, query2res) {
// use query1res and query2res
});
Turns out it was down to a function deeper in the code, which needed to return a promise to chain off of. after adding this the code was happy enough. The function that needed to return the promise was called in the forEach and has nothing to do with the initial two queries, which is what was throwing me.
I'm really trying to wrap my head around angular's service/factory/provider constructs before I refactor a pretty big project.
I've read lots of docs and articles on services vs. factories and thought I understood how each of them are created and what they do.
However, while trying stuff I attempted to use a service in a factory or two...
This was really useful: I now understand that there is only one of my 'jsonService' (it's a singleton), so this simple approach WILL NOT WORK... (I'll need each factory to have a separate instance of something)
.service('jsonService', ['$http', function ($http) {
var data= {'msg':'no data'};
this.serviceData= data;
this.get= function(url){
$http.get(url).then(function (resp) {
data= resp.data;
});
}
}])
.factory('fac1', ['jsonService', function(jsonService){
jsonService.get('json/data1.json');
return jsonService.serviceData;
}])
.factory('fac2', ['jsonService', function(jsonService){
jsonService.get('json/data2.json');
return jsonService;
}])
When I use the factories in a controller like:
myController.f1= fac1;
myController.f2= fac2.serviceData;
I can see that fac1 and fac2 both return the same object, they both have {msg:'no data'}, if I change one then they both change.
My question is:
Even though I can break on the service and see data= {msg:'no data'} and see it being set to the response data - why do I not see any change in fac1 or fac2 ?
All I can think is that somewhere there must be more than one var data, something is not a 'singleton' ????
EDIT: I have now tried:
this.serviceData= function(){return data;};
and:
myController.f2= fac2.serviceData(); // this is always the 'no data' object
myController.f3= fac2.serviceData;
if I then (a long time later) call:
var something= myController.f3();
then I do get the json data... but myController.f2 is still {msg:'no data'} why ?
Ok, after trying m.e.conroy's suggestion I finally figured it out...
The problem is nothing to do with angular, it's how javascript passes objects.
(see: Is JavaScript a pass-by-reference or pass-by-value language? )
The factories passed back a 'copy-reference' of the original {msg:'no data'} object, and when the service eventually assigned:
data= resp.data;
that replaced 'data', but the factory-supplied references persist as the old object.
Now, if I do:
.service('jsonService', ['$http', function ($http) {
var data= {'msg':'no data', 'result':null};
this.serviceData= data;
this.get= function(url){
$http.get(url).then(function (resp) {
data.result= resp.data; // update properties of the data object
data.msg='got data!'; // instead of replacing it
});
}
}])
...then everything makes sense!
The variables in myController are changed when data arrives (since I have not 'swapped out' the data object).
Obviously, I still have the problem that my two factories return the same object (I'll look at Sacho's suggestion on this) - but I think I've learned something pretty fundamental here.
Try this:
this.serviceData = function(){
return data;
};
Also you probably have a race situation. $http may not have returned by the time the application asks for the serviceData
The difference between angular's "services" and "factories" is miniscule - I suggest just using one or the other, and sticking with it. From this point out, I'll refer to these elements as "services", even though I exclusively use angular.factory to declare them.
For what you ultimately want to do, there is a much simpler solution - simply return the promise from your service.
this.get= function(url){
return $http.get(url)
}
Then in your controller/directive/whatever, just use it like:
jsonService.get(url).then(function success(response) {
// do things with the response
})
But you seem to want to use a service to create many instances, so here's a contrived example achieving that:
.factory('jsonService', jsonService)
jsonService.$inject = ['$http']
function jsonService($http) {
return Fixed
function Fixed(url) {
this.url = url
this.promise = null
this.get = get.bind(this)
function get() {
// This caches the request so you only do it once, for example
if (this.promise == null) {
this.promise = $http.get(url)
}
return this.promise
}
}
}
Then in your controller, you would do something like:
var fixedOne = new jsonService('fixedOne')
fixedOne.get().then(...)
I am writing an app in AngularJs, and I am having some conceptual difficulties regarding promises. More specifically, the role of promises in asynchronously pulling data from an api.
When a user loads the app, the following should happen:
Send AJAX request to api.
Render View.
Receive response from api.
Do stuff with data.
However since the AJAX request (by definition) is asynchronous, the app tries to do stuff with the data before it is returned by the server, which causes object is undefined errors and the like.
At the I have a service something like this
app.service('bookService', ['$http', function($http){
var someData;
$http.get('Some URL').success(function (data){
someData = data.someData;
});
var bookService = {};
bookService.getSomeData = function (){
return someData;
};
return bookService;
}]);
And a controller like this
app.controller('BookController', ['bookService', function(bookService){
console.log(bookService.getSomeData().property);
}]);
The controller loads as soon as the attached view is displayed, and attempts to do something with the someData object. At that point it is still undefined (the http request has not yet returned) an errors occur.
Instead I though something like this might work :
app.service('bookService', ['$http', function($http){
var someDataPromise = $http.get('Some URL');
var bookService = {};
bookService.getSomeData = function (){
someDataPromise.success(function (data){
// This should return the data contained by the promise to the caller
// of the bookService.getSomeData function.
});
};
return bookService;
}]);
The controller would be as before.
My experience with promises, and JavaScript as whole (especially concepts like callbacks and anonymous functions) are quite weak. My suspicion is that, as concept, this probably works, although probably not in the way I have showed here.
Can promises be used in the way that I have shown here, or am I lacking in understanding as to what is going on here?
You should consider changing your service to return a promise as well:
app.service('bookService', ['$http', function($http){
var bookService = {};
bookService.getSomeData = function (){
return $http.get('Some URL');
});
return bookService;
}]);
then your controller can use promises too:
app.controller('BookController', ['bookService', '$scope', function(bookService, $scope){
bookService.getSomeData().success(function(data) {
$scope.data = data;
});
}]);
Your service would usually do a bit more than just retrieve the data but hopefully this demonstrates the concept.
It's worth noting that Angular's data binding will pick up the data as soon as you set it on the scope.