Angular undefined promise - angularjs

I'm new in Angular, and maybe I don't understand everything about promise, so...
I have a resource factory
.factory('Product',["$resource", function ($resource){
var Resource = $resource(
"/api/product/:product_id/",
{product_id: '#id'},
{
query: {
isArray: true,
transformResponse: function (data) {
var items = angular.fromJson(data);
return items.results;
}
},
update: {
method: "PUT",
}
},
{
stripTrailingSlashes: false
}
);
And in my other factory.
.factory('Get',["$http", "Product", function ($http, Product){
return {
getDesc: function(id){
var allProducts = Product.query();
var products = [];
for (var i = 0; i < allProducts.length; i++){
if(allProducts[i].desc == id){
products.push(allProducts[i]);
}
}
return products;
}
}
}])
And in my Controller
$scope.someClick = function(product){
var products = Get.getDesc(product.id);
products.$promise.then(function(data){
$log.log(data);
});
};
And I got: Error: r.$promise is undefined
I don't know why. Could you help me?

There are a few issues with your code. Specifically with the promise:
The query method returns a resource that contains in itself a $promise. so you can do one of the following:
1. Return the promise and do the processing, the filtering (more on that later) in the controller.
2. Use .then on the promise to do the filtering and return that promise:
getDesc: function(id){
var allProductsPromise = Product.query().$promise;
return allProductsPromise.then(function(response){
var products = [];
for (var i = 0; i < response.length; i++){
if(response[i].desc == id){
products.push(response[i]);
}
}
return products;
});
}
The other issue I see is the double filtering. Since your query takes an id, I assume you get only the relevant product. Why do you have to filter the results (iterate and compare the id)? If my observation is correct, you might not need the filtering block and you can return directly the original promise.

Related

AngularJs Service called from two places returns same value

I am new to angular and pardon my ignorance if any. I am trying to create a simple service that will do a get funtionality and serve data into array.
The problem i am having is, no mattter what i do - i always get the same data for any parameter i pass.
Here is my sample service
function myService($http, $q) {
var service = {
getSomeData: getSomeData:
};
var def = $q.defer();
return service;
function getSomeData(category) {
if (category === 'books') {
url = 'http://www.someurl1';
} else {
url = 'http://www.someurl2'
};
$http.get(url, {
params: {
'type': category
}
}).success(function(data) {
def.resolve(data);
}).error(function() {
def.reject('Failed to get data');
});
return def.promise;
}
}
})();
Once i have this, in my controller, i am trying to call it for sample purposes like this
$scope.someData = [] ;
$scope.someData.push(myService.getSomeData('DVDs');
$scope.someData.push(myService.getSomeData('books');
Now when i my look at my $scope.someData,
i have an array of two objects - the problem being that they are always the same and doesnt have data specific to books and dvds.
Another minor issue I have is my object someData has
array --> Promise -- > $$state --> value
which then has the actual data. how can i get the data directly.
i tried return def.promise(data);
One problem is you are only creating one promise outside the getSomeData function.
A promise can only be resolved once. You need a new promise for each request.
Also $http already returns a promise but you can't push it directly into an array as data.
your code should look more like:
Service:
function myService($http, $q) {
var service = {
getSomeData: getSomeData
};
return service;
function getSomeData(category) {
if (category === 'books') {
url = 'http://www.someurl1';
} else {
url = 'http://www.someurl2'
};
return $http.get(url, {
params: {
'type': category
}
}).then(function(response) {
return response.data;
}).catch(function(err) {
console.log("Ooops", err)
});
}
}
Controller
$scope.someData = [] ;
myService.getSomeData('DVDs').then(function(data){
data.forEach(function(item){
$scope.someData.push(item);
});
});

Resolving a promise array

I am having trouble in resolving a promise that is returned by firebase. This is an ionic - angularjs - firebase project that I am building to learn. The issue is that my function returns a promise that contains an array of 3 users but I am unable to unwrap this promise.
My code:
function eventusers(id) {
var userarr = [];
var deferred = $q.defer();
// *The code below makes 2 firebase calls and returns an array of users*
eventref.orderByChild("Eventid").equalTo(eventid).on("value", function(snap) {
var users = snap.val();
angular.forEach(users, function(value,key) {
var obj = value;
for (var prop in obj) {
if(obj[prop] == "True") {
userref.child(prop).on("value", function (snap) {
var id = snap.val().email;
userarr.push(id);
console.log(userarr); // I am able to see the list of users here
});
};
}
});
deferred.resolve(userarr);
});
return deferred.promise;
};
//The console.log shows a promise (pls see the attached pic)
console.log(eventusers(eventid));
// I tried to loop through the response using angular.forEach and also a for loop but it does
//not execute that part of the code as I do not see the response of the console.log. If I
//replace the for loop with just console.log(response), then I get an empty array.
eventusers(eventid).then(function (response) {
for (var i = 0; i <response.length; i++) {
console.log(response[i]);
}
});
Your deferred promise is resolving before the inner asynchronous action
userref.child(prop).on('value', ...
completes.
You'll need to wrap that in another deferred object then return a promise resolving all of the inner promises.
function eventusers(id) {
return $q(function(resolve) {
eventRef.orderByChild('Eventid').equalTo(id).on('value', function(snap) {
var promises = [];
snap.val().forEach(function(user) {
angular.forEach(user, function(userProp, prop) {
if (userProp === 'True') {
promises.push($q(function(resolve) {
userref.child(prop).on('value', function(snap) {
resolve(snap.val().email);
});
}));
}
});
});
resolve($q.all(promises));
});
});
}
eventusers(eventid).then(function (response) {
for (var i = 0; i < response.length; i++) {
console.log(response[i]);
}
});
Modify your code to
eventusers(eventid).then(function (response) {
var myArray = response.value;
for (var i = 0; i <myArray.length; i++) {
console.log(myArray[i]);
};
Since your array object is inside of promise object
You can also refer to Plunker

Why is .then not a function?

service.js
.factory('EventService', function ($http, $cordovaSQLite) {
return {
//some code here..
populateData: function (data) {
var items = [];
for (i = 0; i < data.length; i++) {
items.push(data[i]);
}
return items;
}
}
})
controller.js
.controller('NearCtrl', function ($scope, $http, $cordovaSQLite, EventService) {
EventService.getDataFromDB().then(function (result) {
if (result.length > 0) {
EventService.populateData(result).then(function (items) {
$scope.items = items;
})
} else {
EventService.getDataFromApi().then(function () {
EventService.getDataFromDB().then(function (result) {
EventService.populateData(result).then(function (items) {
$scope.items = items;
})
})
})
}
});
})
When I'm trying to run this code, I get "TypeError: EventService.populateData(...).then is not a function".
What am I doing wrong?
that service needs to return a promise, not returning the items
populateData: function(data) {
var deferred = $q.defer();
var items = [];
for (i = 0; i < data.length; i++) {
items.push(data[i]);
}
deferred.resolve(items);
return deferred.promise;
}
you might not need this though since you could do
var items = EventService.populateData(result);
//Do something with items here
usually promises are used if you're doing something asynchronously. Like calling an API and waiting for a response. In those cases, the response might take seconds to finish THEN the .then function gets called. in your case if you make that function a promise it will be called almost immediately
EDIT: Here's the link to $q Documentation AngularJS: API: $q
Return something that has a promise or just change your calling code:
populateData: return $http.get("www");
or
EventService.getDataFromApi().then(function () {
EventService.getDataFromDB().then(function (result) {
var response = EventService.populateData(result);
$scope.items = response;
});
});
I have same problem, My device is m1 Mac, I fixed it with npm replace yarn to start

a service to provide Restangular objects

I'm trying to make a service that will load persons from the server on demand. The first version looked like this:
services.factory('PersonServiceOld', function(Restangular, ErrorService) {
var persons = [];
var requesting = [];
var get = function(id) {
if (requesting[id]) {
return persons[id];
}
requesting[id] = true;
persons[id] = {'id' : id, 'photoName' : '0.png'};
Restangular.one('persons', id).get().then(function(success) {
persons[id].firstName = success.firstName;
persons[id].lastName = success.lastName;
persons[id].photoName = success.photoName;
}, function(failure) {
requesting[id] = false;
ErrorService.serverError(failure);
});
return persons[id];
};
var reset = function() {
persons = [];
requesting = [];
};
return {
getPerson : get,
clearCache : reset,
};
});
That way I get a reference to an object right away and it will be filled with data slightly after. It worked well... until I noticed that in another use case, I also want to request the address of a person like
var person = PersonService.get(id);
person.one(address).get().then(.......
but the objects returned from my PersonService aren't Restangular objects. So I tried something else:
services.factory('PersonService', function(Restangular, ErrorService) {
var persons = [];
var get = function(id) {
if (!persons[id]) {
persons[id] = Restangular.one('persons', id);
persons[id].get().then(function(success) {
}, function(failure) {
ErrorService.serverError(failure);
persons[id] = null;
});
}
return persons[id]; // also tried: persons[id].$object
};
return {
getPerson : get
};
});
I hope somebody understands what I'm trying to do here and can give me a good pointer on how to achieve this.
Check this Plunkr for a complete example.
As Restangular returns promises, and your get function may be asynchronous or synchronous (in case you use your own cache), you need to create a promise for returning always the same type of object.
You can do it as described in the Angular documentation for $q service.
So your get function may look like :
var get = function (id) {
var deferred = $q.defer();
if (store[id]) {
deferred.resolve(store[id]);
} else {
Restangular.one('person', id).get().then(function (res) {
store[res.id] = res;
deferred.resolve(res);
}, function (err) {
deferred.reject(err);
});
}
return deferred.promise;
}
Then in your controller, for retrieving your data :
PersonService.get(475).then(function (person) {
// stuff
}, function (err) {
// err handling
});

Properly wrap new domain object instance with $resource

As a follow up to $resource to return array of [OO] objects how to properly wrap new domain object instances from $resource
app.factory('NoteResource', ['$resource', function($resource) {
var res = $resource('http://okigan.apiary.io/notes/:id', null, ...);
res.newInstance = function() {
return angular.extend(new Note(), res);
};
}
NoteResource.newInstance() shall return object with standard $save/$update methods.
Best to modify the plunker and watch traffic then "New note" button shall work correctly.
UPDATED
Here is a way you can continue incorporate your Note constructor into your $resource service...
Add a new instance method to the object returned from the factory:
res.getNew = function(){
var newNote = new Note();
newNote.id = undefined; // new note doesn't have id set
newNote.checked = undefined; // don't need this data passed to server?
angular.extend(newNote, res);
return newNote;
}
This method modifies an instantiated Note object (to remove ID and other data that doesn't need to be passed to the server when creating a new note) before merging it with the $resource service and returning that object to the caller.
Call that method from your controller assigning its return value to a local variable. You can also modify its properties from the controller:
var note = NoteResource.getNew();
note.title = 'Eat a lot of junk food';
Call the save method on the note object, passing itself as the sole parameter:
note.save(note).$promise.then(function() {
$scope.notes = NoteResource.query();
$scope.newNotes = [];
});
Watch in dev tools and notice that this does result in a JSON payload being sent with the POST containing your note properties (which your previous solution did not).
Hopefully this solution ticks off your box for enforcing a model without having to using a factory in an unintended manner.
Plunker Demo
Posting my solution for reference:
Latest demo: http://plnkr.co/edit/AVLQItPIfoLwsgDzoBdK?p=preview
Key item to the solution is to wrap returned json objects into javascript objects
with correctly set prototype.
var app = angular.module('plunker', ['ngResource']);
//
function Note() {
this.id = '<new id>';
this.title = '<new title>';
this.checked = false;
this.spellCheck = function() {
// imagine spellchecking logic here
this.checked = true;
};
}
app.factory('NoteResource', function($resource) {
var wrap = function(_) {
// forward declaration -- function redefined below
};
function extend(item) {
return angular.extend(new Note(), item);
}
var url = 'http://okigan.apiary.io/notes/:id';
var res = $resource(url, null, {
create: {
method: 'POST',
transformResponse: function(data, headersGetter) {
var item = angular.fromJson(data);
var headers = headersGetter();
// also handle HTTP 201 response
var extra = {
location: headers.location
};
var model = wrap(item);
angular.extend(model, extra);
return model;
}
},
query: {
method: 'GET',
isArray: true,
transformResponse: function(data, headersGetter) {
var items = angular.fromJson(data);
var models = [];
angular.forEach(items, function(item) {
models.push(wrap(item));
});
return models;
}
},
get: {
method: 'GET',
params: {
id: '#id'
},
transformResponse: function(data, headersGetter) {
var item = angular.fromJson(data);
var model = wrap(item);
return model;
}
}
});
res.url = url;
wrap = function(data) {
var T = Note;
// read up on http://javascript.crockford.com/prototypal.html
T.prototype = res.prototype;
var instance = new T();
angular.extend(instance, data);
return instance;
};
res.newModel = function() {
return wrap({});
};
return res;
});
app.controller('MainCtrl', function($scope, NoteResource) {
$scope.name = 'World';
$scope.notes = NoteResource.query();
$scope.newNotes = [];
$scope.spellCheckAllNotes = function() {
angular.forEach($scope.notes, function(note) {
note.spellCheck();
});
};
$scope.newNote = function() {
var note = NoteResource.newModel();
note.title = 'Buy cheese and bread for breakfast.';
$scope.newNotes.push(note);
note.$save().then(function() {
$scope.notes = NoteResource.query();
$scope.newNotes = [];
});
};
});

Resources