According to AngularJS, my $http call through a service from my controller is returning undefined?
What seems to be the issue here? I am trying to return the data called, but once passed to the controller the data becomes undefined?
JavaScript
var myStore = angular.module('myStore', [])
.controller('StoreController', ['$scope', 'dataService', function ($scope, dataService) {
$scope.products = dataService.getData();
}])
.service('dataService', ['$http', function($http) {
this.getData = function() {
$http.get('assets/scripts/data/products.json')
.then(function(data) {
return data;
});
};
}]);
HTML
<div class="content">
<ul>
<li ng-repeat="product in products.products">{{product.productName}}</li>
</ul>
</div>
I understand that $http, $q, and $resource all return promises, but I thought I had covered that with .then.
The problem could be that you are not returning the promise created by $http.get in your dataService.getData function. In other words, you may solve your undefined issue by changing what you have to this:
.service('dataService', ['$http', function($http) {
this.getData = function() {
return $http.get...
};
}
If you had multiple calls to $http.get within dataService.getData, here is how you might handle them.
.service('dataService', ['$http', function($http) {
this.getData = function() {
var combinedData, promise;
combinedData = {};
promise = $http.get(<resource1>);
promise.then(function (data1) {
combinedData['resource1Response'] = data1;
return $http.get(<resource2>);
});
return promise.then(function (data2) {
combinedData['resource2Response'] = data2;
return combinedData;
});
};
}]);
A much cleaner way, however, would be to use $q.all
.service('dataService', ['$http', '$q', function($http, $q) {
this.getData = function() {
var combinedData, promises;
combinedData = {};
promises = $q.all([
$http.get(<resource1>),
$http.get(<resource2>)
]);
return promises.then(function (allData) {
console.log('resource1 response', allData[0]);
console.log('resource2 response', allData[1]);
return allData;
});
};
}]);
You're problem does lie in the fact that you are not returning a promise but as you stated in #maxenglander's post you may have multiple http calls involved which means you should start creating and resolving your own promise using $q:
.service('dataService', ['$http', '$q', function($http, $q) {
return $http.get('assets/scripts/data/products.json')
.then(function(data) {
//possibly do work on data
return <<mutated data>>;
});
}];
or if you have multiple http calls and need to do some combination work you can do something $q.all:
.service('dataService', ['$http', '$q', function($http, $q) {
var p1 = $http.get('assets/scripts/data/products.json');
var p2 = $http.get('assets/scripts/data/products2.json');
return $q.all([p1, p2]).then(function(result){
//do some logic with p1 and p2's result
return <<p1&p2s result>>;
});
}];
then in your controller you will need to do:
.controller('StoreController', ['$scope', 'dataService', function ($scope, dataService) {
dataService.getData().then(function(result){
$scope.products = result;
});
}]);
What this allows in your service is now you can do complex calls like say call two webservices inside and wait till they are both complete then resolve the promise.
What I'm trying to express here is that you don't need to return the promise provided by the $http.get function, but since you are doing an async action you DO need to return some promise that will be later fulfilled and acted on.
Related
This is my service:
'use strict';
app
.service('myService', function($http) {
this.getJSON = function() {
return $http.get('someUrl/dataForm').then(function(data){
return data.result;
});
};
});
And in my controller I have:
'use strict'
app.controller('myController', function ($scope, myService) {
myService.getJSON().then(function(data){
$scope.myData =data;
});
console.log($scope.myData);
});
I can see that the http call is successfully returning a JSON value but the console log shows that the value of myData is undefined.
What am I doing wrong ?
Place the console.log inside
myService.getJSON().then(function(data){
$scope.myData =data;
console.log($scope.myData);
});
DEMO
Change the code of your controller:
'use strict'
app.controller('myController', function ($scope, myService) {
myService.getJSON().then(function(data){
$scope.myData =data;
console.log($scope.myData);
});
});
This happen because getJSON is an asycronous method, the request to getJSON method not provoke that javascript wait to response, adding console.log in ".then" will solve your issue.
By the way, with getJSON you are working with a concept named "promises", i let you a link explanatory about that with $http
https://docs.angularjs.org/api/ng/service/$http
Update the code in the controller
'use strict';
app.service('myService', function($http) {
this.getJSON = function() {
return $http.get('someUrl/dataForm').then(function(data){
return data.result;
});
};
});
controller
'use strict'
app.controller('myController', function ($scope, myService) {
myService.getJSON().then(function(data){
$scope.myData =data;
console.log($scope.myData);
});
});
$http.get() returns promise object.
promise object has then(), catch(), finally() methods.
then is called when success, catch when error.
change your service to,
app.service('myService', function($http) {
this.getJSON = function() {
return $http.get('someUrl/dataForm'); //returns promise object
};
});
and controller to,
app.controller('myController', function($scope, myService) {
var promise = myService.getJSON();
//after resolving then method get called
promise.then(function(data) {
$scope.myData = data;
console.log($scope.myData);
});
});
I'm trying to receive data using $http as a dependency injection, then assign that data to a promise using angular's $q service. I'm doing something wrong.. can't seem to locate where.
Service:
myApp.factory('githubApi', ['$http', '$q',
function($http, $q) {
var deferred = $q.defer();
//Declaring a promise that will or will not return a users github information.
this.accountInformation = function (){
return $http.get('https://api.github.com/users/joshspears3')
.then(function(response){
deferred.resolve(response);
return deferred.promise;
}, function(response){
deferred.reject(response);
return deferred.promise;
})
}
}
]);
Controller:
myApp.controller('githubCtrl', [ 'githubApi', '$q', '$scope',
function(githubApi, $q, $scope){
githubApi.accountInformation()
.then(
function (result) {
$scope.data = result;
}, function (error) {
// handle errors here
console.log(error.statusText);
}
);
}
]);
Directive:
myApp.directive('githubRequest', [
function() {
return {
scope: {},
restrict: 'E',
controller: 'githubCtrl',
templateUrl: 'public/views/partials/github-request.html'
}
}
]);
Partial (github-request.html):
<p class="component-example-header">Creating a service. Making a $http request to grab my personal Github information.</p>
<div class="service-message">
<p>Avatar:</p>
<img width="20%" src="{{data.avatar_url}}" alt="" />
<p>Username: <span ng-bind="data.login"></span></p>
<p>Followers: <span ng-bind="data.followers"></span>, Following: <span ng-bind="data.following"></span></p>
</div>
<hr>
This is the error that the browser is throwing:
Error: [$injector:undef] http://errors.angularjs.org/1.4.4/$injector/undef?p0=githubApi
at Error (native)
at http://localhost:9999/bower_components/angular/angular.min.js:6:416
at Object.$get (http://localhost:9999/bower_components/angular/angular.min.js:37:32)
at Object.e [as invoke] (http://localhost:9999/bower_components/angular/angular.min.js:39:96)
at http://localhost:9999/bower_components/angular/angular.min.js:40:410
at d (http://localhost:9999/bower_components/angular/angular.min.js:38:308)
at Object.e [as invoke] (http://localhost:9999/bower_components/angular/angular.min.js:39:64)
at Q.instance (http://localhost:9999/bower_components/angular/angular.min.js:80:151)
at K (http://localhost:9999/bower_components/angular/angular.min.js:61:140)
at http://localhost:9999/bower_components/angular/angular.min.js:68:475
It's saying that my githubApi service is undefined when I inject it into my dependencies for possibly my controller?
I think it's because your factory isn't returning anything
myApp.factory('githubApi', ['$http', '$q',
function($http, $q) {
return {
accountInformation: function (){
var deferred = $q.defer();
$http.get('https://api.github.com/users/joshspears3')
.then(function(response){
deferred.resolve(response);
}, function(response){
deferred.reject(response);
})
return deferred.promise;
}
}
}
]);
is how I usually have mine
You should be returning the promise in your service method instead, and the deferred object should be inside the method, also, the service should return an object:
myApp.factory('githubApi', ['$http', '$q',
function($http, $q) {
//Declaring a promise that will or will not return a users github information.
return {
accountInformation: function () {
var deferred = $q.defer();
$http.get('https://api.github.com/users/joshspears3')
.then(function(response){
deferred.resolve(response);
}, function(response){
deferred.reject(response);
});
return deferred.promise;
}
}
}
]);
You can also simplify this as the $http service already returns a promise:
myApp.factory('githubApi', ['$http',
function($http) {
//Declaring a promise that will or will not return a users github information.
return {
accountInformation: function () {
return $http.get('https://api.github.com/users/joshspears3');
}
}
}
]);
First, $http.get returns a $q promise already, there is no need to wrap it in a promise again.
Second, a factory creates a single instance of a service which is shared. Therefore the method for the factory should return that object. Assigning anything to this within the factory function itself will not expose that method / property:
myApp.factory('githubApi', ['$http', function ($http) {
var githubApi = {
accountInformation: function () {
return $http.get('https://api.github.com/users/joshspears3');
}
};
return githubApi;
}
The controller isn't picking up the data correctly from the factory for it to display in the view (or the factory syntax isn't correct for the controller). I initially had all the data in the controller and it worked fine but when I transferred it into the factory the data would no longer display on the view. So either the controller isnt calling the factory data correctly or the factory isnt defined correctly, and I don't know which one is wrong.
Contoller:
app.controller('dbCtrl', ['$scope', 'myfactory', function($scope, myfactory) {
myfactory.success(function(data) {
$scope.test1 = results[0].data;
$scope.test2 = results[1].data;
$scope.test3 = results[2].data;
});
}]);
Factory:
app.factory('myfactory', ['$http', function($http, $q) {
$q.all([
$http.get('/url1'),
$http.get('/url2'),
$http.get('/url3')
]).then(function(data) {
return data;
})
}]);
your code syntax for creating and accessing factory is wrong. you should check angular docs.
But to your problem, I have created example matching yours.
http://plnkr.co/edit/TJObJN?p=preview
myfactory.getResult().then(function(results) {
....
}
app.factory('myfactory', ['$http', '$q', function($http, $q) {
var _getResult = function() {
// returns promise which depends on all 3 http responses.
// parallel AJAX request.
return $q.all([
$http.get('/url1'),
$http.get('/url2'),
$http.get('/url3')
]);
};
// public functions available in controller
return {
getResult: _getResult
};
///////////////////
}]);
few notes:
Angular factory you create should return Object containing functions or properties.
make sure you add all dependancies in array as well as in function arguments.
$q promise only has then. while $http promise gives success(...) callback.
Check out syntax and examples here
app.factory('myfactory', ['$http', function($http, $q) {
return {
getAll: $q.all([
$http.get('/url1'),
$http.get('/url2'),
$http.get('/url3')
]).
then(function(data) {
return data;
});
};
}]);
And then in ctrl:
myfactory.getAll().then( ...
I am very new to angularjs and am having a hard time trying to figure out this issue.
Basically, we are using a factory to request data for our application. When the factory returns a promise, we were hoping that the data inside the returned promise that was defined in our scope, would be able to be used, but it is only returning as text on the page.
For example: We have defined $scope.name in our controller:
app.controller('AccountController',function($scope,Account) {
$scope.name = 'Abby';
$scope.news = [];
Account.getSnapshot().success(function(data) {
$scope.news.push(data);
});
});
so the factory (getSnapshot) will return something like "Hello {{name}}" from an $http request as follows:
app.factory('Account',function($http) {
return {
getSnapshot : function() {
return $http.get('data.php');
}
}
});
Is it possible to allow the factory to access /use {{name}} from the $scope?
You will need to use internal Angular $interpolate service:
app.controller('AccountController', function($scope, $interpolate, Account) {
$scope.name = 'Abby';
$scope.news = [];
Account.getSnapshot().success(function(data) {
var text = $interpolate(data)($scope);
$scope.news.push(text);
});
});
Use $q and promises thanks to #dfsq's answer on my post similar to this. Works perfectly.
Here's a plunker.
// Factory method.
app.factory('Account', function($http, $q) {
var data;
return {
getSnapshot: function() {
return data ? $q.when(data) : $http.get('data.json').then(function(response) {
data = response.data;
return data;
})
}
}
});
// Controller method.
app.controller('AccountController', function($scope, Account) {
$scope.name = 'Abby';
$scope.news = [];
Account.getSnapshot().then(function(data) {
$scope.news = data;
});
});
Is it possible to pass a promise to a UI.Router $state from an outside controller (e.g. the controller that triggered the state)?
I know that $state.go() returns a promise; is it possible to override that with your own promise resolve this promise directly yourself or resolve it using a new promise?
Also, the documentation says the promise returned by $state.go() can be rejected with another promise (indicated by transition superseded), but I can't find anywhere that indicates how this can be done from within the state itself.
For example, in the code below, I would like to be able to wait for the user to click on a button ($scope.buttonClicked()) before continuing on to doSomethingElse().
I know that I can emit an event, but since promises are baked into Angular so deeply, I wondered if there was a way to do this through promise.resolve/promise.reject.
angular.module('APP', ['ui.router'])
.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('myState', {
template: '<p>myState</p>',
controller: ['$state', '$scope', '$q', function ($state, $scope, $q) {
var deferred = $q.defer();
$scope.buttonClicked = function () {
deferred.resolve();
}
}]
});
}])
.controller('mainCtrl', ['$state', function ($state) {
$state.go('myState')
.then(doSomethingElse)
}]);
Update
I have accepted #blint's answer as it has got me closest to what I wanted. Below is some code that fleshes out this answer's idea a bit more. I don't think the way I have written this is a very elegant solution and I am happy if someone can suggest a better way to resolve promises from a triggered state.
The solution I've chosen is to chain your promises as you normally would in your controller, but leave a $scope.next() method (or something similar) attached to that scope that resolves/rejects the promise. Since the state can inherit the calling controller's scope, it will be able to invoke that method directly and thus resolve/reject the promise. Here is how it might work:
First, set up your states with buttons/controllers that call a $scope.next() method:
.config(function ($stateProvider) {
$stateProvider
.state('selectLanguage', {
template: '<p>Select language for app: \
<select ng-model="user.language" ng-options="language.label for language in languages">\
<option value="">Please select</option>\
</select>\
<button ng-click="next()">Next</button>\
</p>',
controller: function ($scope) {
$scope.languages = [
{label: 'Deutch', value: 'de'},
{label: 'English', value: 'en'},
{label: 'Français', value: 'fr'},
{label: 'Error', value: null}
];
}
})
.state('getUserInfo', {
template: '<p>Name: <input ng-model="user.name" /><br />\
Email: <input ng-model="user.email" /><br />\
<button ng-click="next()">Next</button>\
</p>'
})
.state('mainMenu', {
template: '<p>The main menu for {{user.name}} is in {{user.language.label}}</p>'
})
.state('error', {
template: '<p>There was an error</p>'
});
})
Next, you set up your controller. In this case, I'm using a local service method, user.loadFromLocalStorage() to get the ball rolling (it returns a promise), but any promise will do. In this workflow, if the $scope.user is missing anything, it will progressively get populated using states. If it is fully populated, it skips right to the main menu. If elements are left empty or are in an invalid state, you get taken to an error view.
.controller('mainCtrl', function ($scope, $state, $q, User) {
$scope.user = new User();
$scope.user.loadFromLocalStorage()
.then(function () {
var deferred;
if ($scope.user.language === null) {
deferred = $q.defer();
$state.go('selectLanguage');
$scope.next = function () {
$scope.next = undefined;
if ($scope.user.language === null) {
return deferred.reject('Language not selected somehow');
}
deferred.resolve();
};
return deferred.promise;
}
})
.then(function () {
var deferred;
if ($scope.user.name === null || $scope.user.email === null) {
deferred = $q.defer();
$state.go('getUserInfo');
$scope.next = function () {
$scope.next = undefined;
if ($scope.user.name === null || $scope.user.email === null) {
return deferred.reject('Could not get user name or email');
}
deferred.resolve();
};
return deferred.promise;
}
})
.then(function () {
$state.go('mainMenu');
})
.catch(function (err) {
$state.go('error', err);
});
});
This is pretty verbose and not yet very DRY, but it shows the overall intention of asynchronous flow control using promises.
The purpose of promises is to guarantee a result... or handle a failure. Promises can be chained, returned in functions and thus extended.
You would have no interest in "overriding" a promise. What you can do, however:
Handle the failure case. Here's the example from the docs:
promiseB = promiseA.then(function(result) {
// success: do something and resolve promiseB
// with the old or a new result
return result;
}, function(reason) {
// error: handle the error if possible and
// resolve promiseB with newPromiseOrValue,
// otherwise forward the rejection to promiseB
if (canHandle(reason)) {
// handle the error and recover
return newPromiseOrValue;
}
return $q.reject(reason);
});
Append a new asynchronous operation in the promise chain. You can combine promises. If a method called in the chain returns a promise, the parent promised will wall the rest of the chain once the new promise is resolved.
Here's the pattern you might be looking for:
angular.module('APP', ['ui.router'])
.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('myState', {
template: '<p>myState</p>',
controller: 'myCtrl'
});
}])
.controller('myCtrl', ['$scope', '$state', '$q', '$http', 'someAsyncServiceWithCallback',
function ($scope, $state, $q, $http, myService) {
$scope.buttonClicked = function () {
$state.go('myState')
.then(function () {
// You can return a promise...
// From a method that returns a promise
// return $http.get('/myURL');
// Or from an old-school method taking a callback:
var deferred = $q.defer();
myService(function(data) {
deferred.resolve(data);
});
return deferred.promise;
},
function () {
console.log("$state.go() failed :(");
});
};
}]);
Perhaps one way of achieving this would be to return your promise from the state's resolve
resolve: {
myResolve: function($scope, $q) {
var deferred = $q.defer();
$scope.buttonClicked = function () {
deferred.resolve();
}
return deferred.promise;
}
}
There is also an example in resolve docs that may be of interest
// Another promise example. If you need to do some
// processing of the result, use .then, and your
// promise is chained in for free. This is another
// typical use case of resolve.
promiseObj2: function($http){
return $http({method: 'GET', url: '/someUrl'})
.then (function (data) {
return doSomeStuffFirst(data);
});
},