I thought I was doing well with AngularJS & REST until I came across a problem where my data service was not returning data back in time for my model to use it.
So, I have looked at and implemented a promise, but it is still not delaying until the HTTP REST call is done.
I would appreciate any help from the enlightened.
My routing passes a booking Id to the bookingController, which retrieves the booking details and then displays them in an editable form in booking.html template.
testLabApp.controller('bookingController', function ($q, $scope, $location, $window, $routeParams, service) {
if ($routeParams.id) {
$scope.bId = $routeParams.id;
//Expected this to setup my promise
var bookdefer = $q.defer();
bookdefer.promise
.then(function (booking) {
//When successful the result is assigned to my booking in the controller $scope
$scope.booking = booking;
//I am making a copy for form updates
$scope.editableBooking = angular.copy($scope.booking);
//When it runs console displays error:-
// "TypeError: Unable to get property 'Storeno' of undefined or null reference"
console.log("[bookingCtrl] 1 New: " + $scope.booking.Storeno + " and Editable: " + $scope.editableBooking.Storeno);
});
// Is this not calling my getBooking service function with the Id passed into my controller?
bookdefer.resolve(service.getBooking($scope.bookingId));
}
else {
//...
}
When code get to '[bookingCtrl] 1...' the console displays an error "TypeError: Unable to get property 'Storeno' of undefined or null reference", which makes me think that the booking data hasn't been retrieved.
then after this message the console displays:
[getBooking] Done = Id: 209 | Store no: 9180 | Description: test | Status: Booked
My data service includes a number of functions which make REST calls:-
testLabApp.factory('service', ['$rootScope', '$http', function ($rootScope, $http) {
var service = {};
$http({
method: 'GET',
url: "/_api/web/lists/GetByTitle('Bookings')/Items?$filter=Id eq '" + bookingId + "'",
headers: {
'Accept': 'application/json; odata=verbose'
},
}).success(function (d) {
var e = d.d.results[0];
booking = {
Id: e['Id'],
Storeno: e['Title'],
BookedBy: e['BookedBy'],
Description: e['Description'],
StartDate: e['StartDate'],
EndDate: e['EndDate'],
Status: e['Status']
};
console.log("[getBooking] Done = Id: " + booking.Id + " | Store no: " + booking.Storeno + " | Description: " + booking.Description + " | Status: " + booking.Status);
return booking;
}).error(function (er) {
console.log("[getBooking] http error : " + er);
});
};
Thanks again for any helps or suggestions.
Regards
Craig
The success function in your service is executed asynchronously. As a consequence, your service actually returns null to your controller. Here's what I've done in all my angular projects:
service.getStuff = function(id) {
var dfd = $q.defer();
$http({
method: 'GET',
url: '/api/some-end-point/id'
}).then(function(data) {
var result = data.data;
dfd.resolve(result);
}, function(error) {
$log.error(error);
dfd.reject(error);
});
return dfd.promise;
}
And my controllers are written like this:
if ($routeParams.id) {
$scope.bId = $routeParams.id;
//Expected this to setup my promise
service.getStuff($routeParams.id).then(
function(data){
$scope.booking = data;
},
function(error){
console.log(error);
}
);
}
'Below is an example of an asynchronous get call from a controller using a service. You can actually return $http.get() to your controller in order to not need to create a new deferred object:
(function(){
angular.module('app', []);
angular.module('app').factory('service', ['$http', function($http){
return {
getData: getData;
}
function getData(){
return $http.get('/url')
.success(function(data){
//Do business logic here
return data;
})
.error(function(error){
return error;
});
}
}]);
angular.module('app').controller('controller', ['$scope', 'service', function($scope, service){
service.getData()
.success(function(data){
$scope.data = data;
})
.error(function(error){
//error logic
});
}]);
})();
Related
I'm developing an e-commerce web app using AngularJS (v1.6.7) and Parse Server (v2.3.3).
I created Category and Product class in Parse Server. I'm trying to fetch in a certain amount of products per category.
For example, in homepage, 20 products will be retrieved per category. The amount of products changes in other pages.
I want to do it using a factory that fetches given amount of products in any category (amount and category of products will be passed to the function as parameters). So I'll be able to reuse it inside other controllers.
ProductsFactory factory:
sebetimapp.factory('ProductsFactory', ['$q', function($q){
Parse.initialize('MY_APP_ID', 'JS_KEY');
Parse.serverURL = 'https://parseapi.back4app.com/';
let fac = {};
fac.getProducts = function(cat, lmt) {
let Category = Parse.Object.extend('Category'),
qr = new Parse.Query(Category);
qr.get(cat, {
success: function (res) {
let product_dfd = $q.defer(),
Product = Parse.Object.extend('Product'),
query = new Parse.Query(Product);
query.include('category');
query.equalTo('category', res);
if (lmt) {
query.limit(lmt);
}
query.find({
success: function(results) {
product_dfd.resolve(results);
},
error: function(err) {
product_dfd.reject(results);
}
});
return product_dfd.promise;
},
error: function(object, error) {
//
}
});
};
return fac;
}]);
productsCtrl controller:
sebetimapp.controller('productsCtrl', ['$scope', '$log', '$location', '$q', 'ProductsFactory', function($scope, $log, $location, $q, ProductsFactory) {
let params = $location.search(); // To grab category ID from URL.
ProductsFactory.getProducts(params.cat, 20).then(function(response) {
$log.log('Successfully retrieved products.');
}, function(error) {
$log.log('Unable to get products.');
});
}]);
When I execute it, an error occurs:
TypeError: Cannot read property 'then' of undefined
But if I don't use this factory and define getProducts() function inside my controller, it works fine.
Why is this happening? I'm new to AngularJS. Any help would be appreciated.
The .then() method is only available on Promises. Your function appears to be not returning anything (and hence, .then() is unavailable).
This might help:
sebetimapp.factory('ProductsFactory', ['$q', function($q) {
Parse.initialize('MY_APP_ID', 'JS_KEY');
Parse.serverURL = 'https://parseapi.back4app.com/';
var fac = {};
fac.getProducts = function(cat, lmt) {
var Category = Parse.Object.extend('Category'),
qr = new Parse.Query(Category);
return qr.get(cat)
.then(function(res) {
var Product = Parse.Object.extend('Product'),
query = new Parse.Query(Product);
query.include('category');
query.equalTo('category', res);
if (lmt) {
query.limit(lmt);
}
return query.find();
});
};
return fac;
}]);
Most methods in the Parse JS API return promises. You can use those directly (and not use the success and error callbacks). It's been ages since I worked on Parse (I thought it was no longer available) so you may have to figure out the details yourself.. Handy Link: http://docs.parseplatform.org/js/guide/#promises
TLDR; Your factory function needs to return a promise but is returning nothing and hence .then() is unavilable
EDIT: Here is another way to the same thing with minimal changes to you original code (this is not the best way to do this, however)
sebetimapp.factory('ProductsFactory', ['$q', function($q) {
Parse.initialize('MY_APP_ID', 'JS_KEY');
Parse.serverURL = 'https://parseapi.back4app.com/';
var fac = {};
fac.getProducts = function(cat, lmt) {
var Category = Parse.Object.extend('Category'),
qr = new Parse.Query(Category),
// Move the deffered object out of the inner function
product_dfd = $q.defer();
qr.get(cat, {
success: function(res) {
var Product = Parse.Object.extend('Product'),
query = new Parse.Query(Product);
query.include('category');
query.equalTo('category', res);
if (lmt) {
query.limit(lmt);
}
query.find({
success: function(results) {
product_dfd.resolve(results);
},
error: function(err) {
product_dfd.reject(results);
}
});
},
error: function(object, error) {}
});
// Return the deferred object
return product_dfd.promise;
};
return fac;
}]);
This question already has answers here:
AngularJS passing data to $http.get request
(7 answers)
Closed 5 years ago.
I'm trying to pass the value of an identifier through a url to an express router, the variable I want to pass is IDR that I got it correctly from $routeParams.IDR and now I have to pass it by $http, but I can't do it.
That is my controller.js
( $routeParams.IDR work good)
app.controller("rutaDestinosCtrl", function($scope, $http, userService, $routeParams){
vm = this;
vm.destinos = [];
var requestData = {
'IDR': $routeParams.IDR //$routeParams.IDR is for example: 5
};
vm.funciones = {
obtenerDestinos : function(){
$http({
method: "GET",
url: '/privadas/rutas/obtenerDestinosRuta',
requestData,
headers: {'auth-token': userService.token}
})
.then(function(respuesta){
vm.destinos = respuesta.data.data;
}, function(respuesta){
console.log("Error:", respuesta.status);
})
} //obtenerDestinos
}//funciones
vm.funciones.obtenerDestinos();
});
After passing the token filter and arriving at this method correctly:
router.get('/obtenerDestinosRuta', function(req,res){
var query = "SELECT * FROM public.\"Destino\" D " +
" JOIN public.\"RutaDestino\" RD ON D.\"IDD\" = RD.\"IDD\"" +
" WHERE \"IDR\" = " + req.body.IDR+ " ORDER BY D.\"IDD\" ASC";
console.log(query);
db.query(query).spread(function(result, metadata){
res.json({
data: result
})
}).catch(function(err){
res.status(500).send("Error: "+ err);
})
});
I can't get the value I had stored in RequestData (which would be 5), I tried it several ways without result, in console "req.body.IDR" is undefined, and if I change "req.body.IDR" in the statement by the value 5, works perfectly, any idea of how it can be done?
This it's the query result from the console.log:
SELECT * FROM public."Destino" D JOIN public."RutaDestino" RD ON
D."IDD" = RD."IDD" WHERE "IDR" = undefined ORDER BY D."IDD" ASC
Regards.
Can you put also your route definition?
In your route definition you have to specify the expected parameters, as in:
module.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/routeName/:IDR', {templateUrl: 'template.html', controller: myCtrl})
}]);
Note the ":IDR" value in the route.
Then in your controller you can access it with $routeParams.IDR
Thanks guys, finally I find other post with the same problem: angular.http params
But, on resume Angular.http provides an option for send params by $http, called 'params'. That its good if you have to use the method "GET". On my case, after use it, work perfect. That its my code:
On controller.js I have used params and config:
app.controller("rutaDestinosCtrl", function($scope, $http, userService, $routeParams){
console.log("Controlador rutaDestinosCtrl");
vm = this;
vm.destinos = [];
vm.funciones = {
obtenerDestinos : function(){
var data = {
IDR: $routeParams.IDR
};
var config = {
params: data,
headers : {'auth-token': userService.token}
};
$http.get('/privadas/rutas/obtenerDestinosRuta', config)
.then(function(respuesta){
//Acciones cuando sea una respuesta correcta
vm.destinos = respuesta.data.data;
}, function(respuesta){
//Acciones cuando sea una respuesta erronea
console.log("Error:", respuesta.status);
})
} //obtenerDestinos
}//funciones
vm.funciones.obtenerDestinos();
});
And to get the value of IDR, I have used req.query.IDR.
router.get('/obtenerDestinosRuta', function(req,res){
console.log(req.query.IDR);
var query = "SELECT * FROM public.\"Destino\" D " +
" JOIN public.\"RutaDestino\" RD ON D.\"IDD\" = RD.\"IDD\"" +
" WHERE \"IDR\" = " + req.query.IDR + " ORDER BY D.\"IDD\" ASC";
console.log(query);
db.query(query).spread(function(result, metadata){
res.json({
data: result
})
}).catch(function(err){
res.status(500).send("Error: "+ err);
})
});
This seems like a simple problem, and I must be overlooking something small.
I have a function that accesses Spotify API and searches for an artist. I know that accessing this route via a normal URL returns a result. (e.g. http://localhost:3001/search?artist=%27Linkin%20Park%27) Here the code that does that:
router.get('/search', function(req, res, next)
{
var artist = req.param('artist');
console.log("Artist: " + artist);
smartSpot.getArtistID(artist, function(data)
{
console.log("Data: " + data);
res.json(data.id);
});
});
Then, there is the code on the front end to search for the artist. This is all done via angular.
angular.module('smart-spot', [])
.controller('MainCtrl', [
'$scope', '$http',
function($scope, $http)
{
$scope.createPlaylist = function()
{
var artist = $scope.artist;
console.log(artist);
window.open("/login", "Playlist Creation", 'WIDTH=400, HEIGHT=500');
return $http.get('/search?=' + $scope.artist) //this doesn't pass in the artist
.success(function(data)
{
console.log(data);
});
}
}
]);
The $http.get() does not pass in the $scope.artist` value properly.
Looks like you might be missing the "artist" query param in your string concatenation.
$http.get('/search?artist=' + $scope.artist)
Alternatively, you could pass the artist as a query param.
function createPlaylist() {
return $http.get('/search', { params : { artist : $scope.artist } })
.then(function(response) {
return response;
}, function(error) {
return $q.reject(error);
});
}
Also, I would avoid using .success. I believe that's depreciated in favor of the syntax above. First param is success function, second is fail function.
you can pass parameters via
$http.get('/search', {
params: {
artist: $scope.artist
}
})
.success(function(data)
{
console.log(data);
});
I am trying to get data back from a web service, I have to approaches the first is calling the data from the controller which works here is the code
$http({
method: 'POST',
url: 'https://url_json.php',
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset-UTF-8'},
transformRequest: function(obj) {
var str = [];
for(var p in obj)
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return str.join("&");
},
data: paramsVal
}).then(function(response){
$scope.myData = response.data.PlaceDetailsResponse.results[0].first_name;
console.log('my data',$scope.myData);
});
}
but I would like to share the data between controllers so I read service is the best options, so in my services.js I have:
.factory('userService', function($http){
return {
getUsers: function(){
return $http.post("https://url_json.php",
{entry : 'carlo' ,
type:'first_name',
mySecretCode : 'e8a53543fab6f00ebec85c535e'
}).then(function(response){
users = response;
return users;
});
}
}
})
but when I call it from the controller with var user = userService.getUsers(); returns the below in the console:
user is [object Object]
and inspecting element within chrome I only see:
user is Promise {$$state: Object}
Also in chrome when I drill down on Promise the data value = "".
Can anyone take a look at my services and see if I'm doing anything wrong.
Thanks
.then returns a promise. You're falling for the explicit promise construction antipattern.
getUsers: function(){
return $http.post("https://url_json.php",
{entry : 'carlo' ,
type:'first_name',
mySecretCode : 'e8a53543fab6f00ebec85c535e'
})
}
The above is all you need. Then, in your controller:
userService.getUsers()
.then(function(response) {
$scope.data = response.data; // or whatever
});
I would recommend looking at John Papa AngularJS style guide. He has a lot of information about fetching service data, using it in controllers, etc.
In your controller you will have to assign the user variable by resolving the promise return from the getUsers like below:
$scope.user = [];
$scope.error ="";
userService.getUsers().then(function(data){
$scope.user = data
},function(err){
$scope.error = err;
});
Hope this helps.
I am looking to have parameters in the route, by using a colon before the variable name
// dynamic pages for each ITEM, once selected
// from $routeParams.itemID in ItemCtrl
.when('/:itemID', {
templateUrl: 'views/item.html',
controller: 'ItemController'
})
When a div-box is clicked, Angular should route to the specific item
<div class="itemBox" ng-click="getItem(item._id)">
Right now, the call to the node/express API seems to be working
[16:36:18.108] GET http://localhost:8080/api/items/534240001d3066cc11000002 [HTTP/1.1 304 Not Modified 4ms]
But this error logs in the console:
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (http.js:691:11)
...
at Promise.<anonymous> (/Users/Username/Downloads/project/v19/app/routes.js:41:8)
...
Line 41 (for 41:8?) in routes.js is res.json(item);
// load the item model
var Item = require('./models/item');
// get One item
app.get('/api/items/:item_id', function(req, res) {
// use mongoose to get the one item from the database
Item.findById({
_id : req.params.item_id
},
function(err, item) {
// if there is an error retrieving, send the error. nothing after res.send(err) will execute
if (err)
res.send(err)
res.json(item); // return the item in JSON format
});
});
Though it seems like the issue might be in the Controller because all of the other API calls work.. And so I tried passing $routeParams all over the place!
angular.module('ItemCtrl', [])
// inject the Item service.factory into our controller
.controller('ItemController', function($scope, $routeParams, $http, Items, isEmptyObjectFilter) {
// get an Item after clicking it
$scope.getItem = function(id, $routeParams) {
Items.getOne(id, $routeParams)
// if successful getByID, call our function to get the Item data
.success(function(data, $routeParams) {
// assign our Item
$scope.item = data;
// for use with a parameter in appRoutes.js using itemID as the variable
$scope.itemID = $routeParams.itemID;
})
.error(function(data) {
console.log('Error: ' + data);
});
};
});
Or maybe it's the service? Does this need to pass $routeParams as function(id, $routeParams)
angular.module('ItemService', [])
// super simple service
// each function returns a promise object
.factory('Items', function($http) {
return {
get : function() {
return $http.get('/api/items');
},
getOne : function(id) {
return $http.get('/api/items/' + id);
},
create : function(itemData) {
return $http.post('/api/items', itemData);
},
delete : function(id) {
return $http.delete('/api/items/' + id);
}
}
});
Would really appreciate some assistance debugging this.. Thanks
It looks like you are getting the data correctly. The problem is that you want the route to change after successfully getting the API call?
$routeParams won't change the route for you. That just gets the data. Use $location to change the route.
.controller('ItemController', function($scope, $routeParams, $location, $http, Items, isEmptyObjectFilter) {
$scope.getItem = function(id) {
Items.getOne(id)
.success(function(data) {
$scope.item = data;
$scope.itemID = $routeParams.itemID;
// redirect
$location.path('/' + $routeParams.itemID);
});
});
});
Since all of your data seems to be ready to go, you just need Angular to redirect to the route. $location should be the way to go.
That message is because you are getting an error and executing the res.send() method, and after that you have res.json(), express is trying to respond twice.
Try changing:
if (err)
res.send(err)
To:
if (err) {
res.json({ error: err });
} else {
var object = item.toObject();
res.json(object);
}
Angular resource example:
angular.module('ItemService')
.factory('Items', ['$resource', function($resource) {
return $resource('/api/items/:itemID', {
itemID: '#_id'
}, {
update: {
method: 'PUT'
}
});
}]);
Now you can do this in your controller:
// Find
Items.get({
itemID: $routeParams.itemID
}, function(item) {
$scope.item = item;
});
// Update
$scope.item.name = 'New name';
$scope.item.$update();
// Remove
$scope.item.$remove();