AngularJS: How to wait for $resource to finish? - angularjs

I have a controller:
app.controller('ProductDetailCtrl', ['$scope', '$q', '$resource', 'Product', function($scope, $q, $resource, Product) {
$scope.product = {};
$scope.init = function(id)
{
$scope.product = Product.get({productId: id});
}
$q.all([$scope.product.$promise]).then(function() {
$scope.selected_color = $scope.product.products_colors[0];
});
// $scope.selected_color = $scope.product.products_colors[0];
}]);
And a factory:
app.factory('Product', ['$resource', function($resource) {
return $resource("/api/products/:productId", {}, {
query: {method: 'GET', isArray: true},
});
}]);
However, when I load the page it gives me this:
Error: $scope.product.products_colors is undefined #http://localhost:3000/assets/products/controllers/productscontroller.js?body=1:11:5 qFactory/defer/deferred.promise.then/wrappedCallback#http://localhost:3000/assets/angular.js?body=1:11499:15 qFactory/ref/<.then/<#http://localhost:3000/assets/angular.js?body=1:11585:11 $RootScopeProvider/this.$get</Scope.prototype.$eval#http://localhost:3000/assets/angular.js?body=1:12609:9 $RootScopeProvider/this.$get</Scope.prototype.$digest#http://localhost:3000/assets/angular.js?body=1:12421:15 $RootScopeProvider/this.$get</Scope.prototype.$apply#http://localhost:3000/assets/angular.js?body=1:12713:13 bootstrap/doBootstrap/<#http://localhost:3000/assets/angular.js?body=1:1420:9 invoke#http://localhost:3000/assets/angular.js?body=1:3919:7 bootstrap/doBootstrap#http://localhost:3000/assets/angular.js?body=1:1419:1 bootstrap#http://localhost:3000/assets/angular.js?body=1:1432:5 angularInit#http://localhost:3000/assets/angular.js?body=1:1345:5 #http://localhost:3000/assets/angular.js?body=1:21818:5 jQuery.Callbacks/fire#http://localhost:3000/assets/jquery.js?body=1:3100:1 jQuery.Callbacks/self.fireWith#http://localhost:3000/assets/jquery.js?body=1:3212:7 .ready#http://localhost:3000/assets/jquery.js?body=1:3424:3 completed#http://localhost:3000/assets/jquery.js?body=1:3454:3
return logFn.apply(console, args);
How would I wait until the $resource finishes loading? I want to set one of my $scope variables based on the result of Product.get(..). I know that Product.get(..) is returning and object with a products_colors property because console.log says so

Use a callback:
Product.get({productId: id}, function(data) {
$scope.product = data;
});

Related

What should I test for my angular controller?

I've learning to use Karma to test my angularjs app. However, not a few of my controllers use multiple services which are http requests that retrieve json which is then loaded to the page. I've been stuck because of two questions I can't answer. 1) How do I mock these services 2) what exactly should I test for my controller Question 2 I find difficult to answer because controller functions depend on services which I use in other controllers. Anyway, let me show one of my controllers and then, the "library" housing my services:
One of my controllers
angular.module('ccApp')
.controller('CountriesCtrl', ['$scope', '$routeParams', '$location','countryInfo', 'getCountries', 'countriesCache', 'getNeighbors',
'buildCountry', '$timeout', '$q',
function($scope, $routeParams, $location, countryInfo, getCountries, countriesCache, getNeighbors,
buildCountry, $timeout, $q){
getCountries.countriesObject.then(function(response){
$scope.geocountries = response.data.geonames;
},
function(response){
alert("error");
});
$scope.toCountry = function(geocountry){
getNeighbors(geocountry.geonameId)
.then(function(response){
buildCountry(geocountry, response);
var path = '/countries/'+countryInfo.name+'/capital';
$location.path(path);
}),
function(response){
alert('Error');
};
};
$scope.goHome = function(){
$location.path('/');
};
}]);
What here should I test in the controller spec?
Here's the library where the services are housed:
angular.module('library', [])
.service('countryInfo', function(){
var country = {
name: '',
pop: '',
area: '',
capital: '',
code: '',
capPop: '',
numNeigh: 0,
neighbors: []
};
return country;
})
.factory('countriesCache', ['$cacheFactory', function($cacheFactory){
return $cacheFactory('countriesCached');
}])
.factory('getCountries', ['$http', function($http){
var request = {
username: 'vman'
};
return { countriesObject : $http({
method: 'GET',
url: 'http://api.geonames.org/countryInfoJSON',
params: request
})};
}])
.factory('getCountry', ['$http', function($http){
return function(countryCode){
return $http({
method: 'GET',
url: 'http://api.geonames.org/countryInfoJSON',
params: { username: 'vman', country: countryCode }
});
};
}])
.factory('getNeighbors', ['$http', function($http){
return function(geonameId){
return $http({
method: 'GET',
url: 'http://api.geonames.org/neighboursJSON',
params: { username: 'vman', geonameId: geonameId }
});
};
}])
.factory('buildCountry', ['countryInfo', '$q', '$timeout', function(countryInfo, $q, $timeout){
return function(geocountry, response){
countryInfo.name = geocountry.countryName;
countryInfo.code = geocountry.countryCode;
countryInfo.pop = geocountry.population;
countryInfo.area = geocountry.areaInSqKm;
countryInfo.capital = geocountry.capital;
countryInfo.neighbors = response.data.geonames;
countryInfo.numNeigh = response.data.geonames.length;
};
}])
.run(['$rootScope', '$location', function($rootScope, $location) {
$rootScope.$on('$routeChangeError', function() {
$location.path('/error');
});
}]);
The following snippet creates a mock of the above service:
module(function($provide) {
$provide.service('demoService', function() {
this.isDemoApi = jasmine.createSpy('isDemoApi');
});
});
//Getting reference of the mocked service
var mockDemoSvc;
inject(function(demoService) {
mockDemoSvc = demoService;
});
To test the controller, firstly you will have to mock all the services which this controller is using by using the above code.
This would help to test controller APIs seperatly. Then you can go ahead to test APIs which are binded with the scope, like: toCountry()

Getting an error while creating factory object

I have a module and controller which are created like :
var API_ENGINE_URL = 'localhost/angular/';
var mainApp = angular.module('mainApp', ['ngRoute', 'ngResource']);
mainApp.controller('productController', ['$scope', 'ProductFactory', 'ProductListFactory','$routeParams', function ($scope, ProductFactory,routeParams, ProductListFactory) {
var productList = new ProductListFactory();// THROWS ERROR HERE
var promise = productList.$get({id: $routeParams.category_id}).$promise;
promise.then(function (productList) {
$scope.productList = productList;
});
}]);
and the Model is created like this, files are properly loaded
mainApp.factory('ProductListFactory', ['$resource',function ($resource) {
return $resource(API_ENGINE_URL + 'category/:id/items', {}, {
get: {method: 'GET', params: {category_id: '#category_id', id: '#id'},isArray:true,url:API_ENGINE_URL+'product?store_id=:storeId'},
save: {method: 'GET', isArray: true}
});
}]);
I am getting an error in the controller like below. what could be the error. Stuck for a long time
In a factory function, the return value is what's cached and injected by Angular. There is no need to instantiate it yourself.
Try this:
mainApp.controller('productController', ['$scope', 'ProductFactory', 'ProductListFactory','$routeParams', function ($scope, ProductFactory,routeParams, ProductListFactory) {
var promise = ProductListFactory.$get({id: $routeParams.category_id}).$promise;
promise.then(function (productList) {
$scope.productList = productList;
});
}]);
Problem is in ordering should be :
['$scope', 'ProductFactory', 'ProductListFactory','$routeParams', function ($scope, ProductFactory,ProductListFactory,$routeParams)

AngularJS : returning data from service to controller

I am trying to create a service to get json and pass it to me homeCtrl I can get the data but when a pass it to my homeCtrl it always returns undefined. Im stuck.
My Service:
var myService = angular.module("xo").factory("myService", ['$http', function($http){
return{
getResponders: (function(response){
$http.get('myUrl').then(function(response){
console.log("coming from servicejs", response.data);
});
})()
};
return myService;
}
]);
My Home Controller:
var homeCtrl = angular.module("xo").controller("homeCtrl", ["$rootScope", "$scope", "$http", "myService",
function ($rootScope, $scope, $http, myService) {
$scope.goData = function(){
$scope.gotData = myService.getResponders;
};
console.log("my service is running", $scope.goData, myService);
}]);
You should return promise from getResponders function, & when it gets resolved it should return response.data from that function.
Factory
var myService = angular.module("xo").factory("myService", ['$http', function($http) {
return {
getResponders: function() {
return $http.get('myUrl')
.then(function(response) {
console.log("coming from servicejs", response.data);
//return data when promise resolved
//that would help you to continue promise chain.
return response.data;
});
}
};
}]);
Also inside your controller you should call the factory function and use .then function to get call it when the getResponders service function resolves the $http.get call and assign the data to $scope.gotData
Code
$scope.goData = function(){
myService.getResponders.then(function(data){
$scope.gotData = data;
});
};
This is an example how I did for my project, it work fine for me
var biblionum = angular.module('biblioApp', []);//your app
biblionum.service('CategorieService', function($http) {
this.getAll = function() {
return $http({
method: 'GET',
url: 'ouvrage?action=getcategorie',
// pass in data as strings
headers: {'Content-Type': 'application/x-www-form-urlencoded'} // set the headers so angular passing info as form data (not request payload)
})
.then(function(data) {
return data;
})
}
});
biblionum.controller('libraryController', function($scope,CategorieService) {
var cat = CategorieService.getAll();
cat.then(function(data) {
$scope.categories = data.data;//don't forget "this" in the service
})
});

AngularJS pass parameter from Controller to Service

I don't know how to pass data from the controller to the service as method arguments... I need to set in the controller the headers variable which is later added to the service which adds this later on to the http call
controller
angular.module('userControllers', []).
controller('userController', ['$scope', '$q', 'Users', '$location',
function($scope, $q, Users, $location) {
$scope.viewUser = function(userId) {
$location.path('/users/'+userId);
};
$scope.createUser = function () {
$location.path('/users/create');
};
headers = {service:'json',resource:'users'};
$scope.users = Users.query(headers);
$scope.users.$promise.then(function(result){
$scope.users = result;
});
}]);
service
angular.module('userServices', ['ngResource']).
factory('Users', ['$resource', '$q', function($resource, $q){
var factory = {
query: function (headerParams) {
var data = $resource('http://localhost:8080/gateway', {}, {
query: {
method: 'GET',
isArray: true,
headers: headerParams
}
});
var deferred = $q.defer();
deferred.resolve(data);
return deferred.promise;
}
}
return factory;
}]);
In this setup I'm getting Error: [$injector:unpr] Unknown provider: UserProvider <- User ... not really sure how to fix this one out ...
Later edit : code cleanup... posted full code and new error
TypeError: Cannot read property 'then' of undefined
at new <anonymous> (http://localhost/frontend-pdi-angular-js/js/controllers/users.js:16:38)
at invoke (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.js:3918:17)
at Object.instantiate (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.js:3929:23)
at https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.js:7216:28
at link (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular-route.js:913:26)
at nodeLinkFn (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.js:6648:13)
at compositeLinkFn (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.js:6039:13)
at publicLinkFn (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.js:5934:30)
at boundTranscludeFn (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.js:6059:21)
at controllersBoundTransclude (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.js:6669:18) <div ng-view="" class="ng-scope">
Later edit #2 : some working code, but using no ngResource whatsoever
angular.module('userControllers', ['userServices']).
controller('userController',
['$scope', '$q', 'Users', '$location', '$http',
function($scope, $q, Users, $location, $http) {
headerParams = {service:'json',resource:'users'};
$scope.users = $http({method: 'GET', url: 'http://localhost:8080/gateway', headers: headerParams, isArray:true}).
success(function(data, status, headers, config) {
$scope.users = data;
console.log(data);
}).
error(function(data, status, headers, config) {
console.log(status);
});
}]);
As others mentioned, you are using a non-existent reference to a provider ($promise) in your code. By simply setting the deferred to $scope.users and then acting on the 'then' of that deferred, you can solve this problem.
You can also simplify it a bit. You are setting the deferred on the $scope object and then you're overwriting that with the resolved value of the deferred. So you can do this instead:
Users.query(headers).then(function(result){
$scope.users = result;
});
if you want to assign the deferred to a separate variable:
var deferred = Users.query(headers);
deferred.then(function(result){
$scope.users = result;
});
angular.module('userControllers', ['userServices']).
controller('userController',
['$scope', '$q', 'Users', '$location',
function($scope, $q, Users, $location) {
headers = {service:'json',resource:'users'};
Users.query(headers).then(function(result){
$scope.users = result;
});
}])
angular.module('userServices', ['ngResource']).
factory('Users', ['$resource', '$q', function($resource, $q){
var factory = {
query: function (headerParams) {
var data = $resource('http://localhost:8080/gateway', {}, {
query: {
method: 'GET',
isArray: true,
headers: headerParams
}
});
var deferred = $q.defer();
deferred.resolve(data);
return deferred.promise;
}
}
return factory;
}]);
Try this. Your not include the userServices module into the userControllers module and you can omit $scope.users = Users.query(headers); part because if you put this this will call the query method.
replace controller with this code:
angular.module('userControllers', []). controller('userController', ['$scope', '$q', 'Users', '$location', function($scope, $q, Users, $location) {
$scope.viewUser = function(userId) {
$location.path('/users/'+userId);
};
$scope.createUser = function () {
$location.path('/users/create');
};
headers = {service:'json',resource:'users'};
Users.query(headers).$promise.then(function(result){
$scope.users = result;
});
}]);
and factory with this code :
angular.module('userServices', ['ngResource']).
factory('Users', ['$resource', '$q', function($resource, $q){
var factory = {
query: function (headerParams) {
return $resource('http://localhost:8080/gateway', {}, {
query: {
method: 'GET',
isArray: true,
headers: headerParams
}
})
}
}
return {
query: factory.query
}
}]);
i think this will help you.

Return factory data to controller always undefined

Trying to return data from the factory and logging within the factory outputs the correct data, but once passed to the controller it is always undefined. If I have my factory logic inside the controller it will work fine. So it must be something simple Im missing here?
Application
var app = angular.module('app', []);
app.controller('animalController', ['$log', '$scope', 'animalResource', function($log, $scope, animalResource) {
$scope.list = function() {
$scope.list = 'List Animals';
$scope.animals = animalResource.get(); // returns undefined data
$log.info($scope.animals);
};
$scope.show = function() {};
$scope.create = function() {};
$scope.update = function() {};
$scope.destroy = function() {};
}]);
app.factory('animalResource', ['$http', '$log', function($http, $log) {
return {
get: function() {
$http({method: 'GET', url: '/clusters/xhrGetAnimals'}).
success(function(data, status, headers, config) {
//$log.info(data, status, headers, config); // return correct data
return data;
}).
error(function(data, status, headers, config) {
$log.info(data, status, headers, config);
});
},
post: function() {},
put: function() {},
delete: function() {}
};
}]);
Log Info
[Object, Object, Object, Object, Object, Object, Object, Object, Object, Object]
200 function (name) {
if (!headersObj) headersObj = parseHeaders(headers);
if (name) {
return headersObj[lowercase(name)] || null;
}
return headersObj;
} Object {method: "GET", url: "/clusters/xhrGetAnimals"}
Your get() method in service is not returning anything. The return inside the success callback only returns from that particular function.
return the $http object
Che this example that is how you use the promises and you return the factory then you access the methods injecting the service on your controller
Use dot syntax to access the function you define on the service
'use strict';
var app;
app = angular.module('app.formCreator.services', []);
app.factory('formCreatorService', [
'$http', '$q', function($http, $q) {
var apiCall, bjectArrarContainer, deferred, factory, webBaseUrl, _getFormElementsData;
factory = {};
deferred = $q.defer();
bjectArrarContainer = [];
webBaseUrl = 'https://tools.XXXX_url_XXXXX.com/XXXXXXX/';
apiCall = 'api/XXXXX_url_XXXX/1000';
_getFormElementsData = function() {
$http.get(webBaseUrl + apiCall).success(function(formElements) {
deferred.resolve(formElements);
}).error(function(err) {
deferred.reject(error);
});
return deferred.promise;
};
factory.getFormElementsData = _getFormElementsData;
return factory;
}
]);
then do it like this for example
'use strict';
var app;
app = angular.module('app.formCreator.ctrls', []);
app.controller('formCreatorController', [
'formCreatorService', '$scope', function(formCreatorService, $scope) {
$scope.formElementsData = {};
formCreatorService.getFormElementsData().then(function(response) {
return $scope.formElementsData = response;
});
}
]);

Resources