I'm using a service to make user data available to various controllers in my Angular app. I'm stuck trying to figure out how to use the $http service to update a variable local to the service (in my case "this.users"). I've tried with and without promises. The server is responding correctly.
I've read several excellent articles for how to use $http within a service to update the scope of a controller. The best being this one: http://sravi-kiran.blogspot.com/2013/03/MovingAjaxCallsToACustomServiceInAngularJS.html. That does not help me though because it negates the benefits of using a service. Mainly, modifying the scope in one controller does not modify throughout the rest of the app.
Here is what I have thus far.
app.service('UserService', ['$http', function($http) {
this.users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
// this.users is undefined here
console.log(this.users);
}
};
promise.then(function() {
// this.users is undefined here
console.log('this.users');
});
}]);
Any help is greatly appreciated. Thank you.
Try using
var users = [];
rather than
this.users = [];
and see what
console.log(users);
outputs in each of those cases.
Your service is oddly defined, but if you have a return in it you can access it from any controller:
app.service('UserService', ['$http', function($http) {
var users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
// this.users is undefined here
console.log(users);
users = data.data;
}
};
return {
getUsers: function(){
return users;
}
}
}]);
so in your controller, you can use:
var myUsers = UserService.getUsers();
UPDATE to use a service correctly here, your service should return a promise and the promise should be accessed in the controller: Here's an example from another answer I gave
// your service should return a promise
app.service('PickerService', [$http', function($http) {
return {
getFiles: function(){
return $http.get('files.json'); // this returns a promise, the promise is not executed here
}
}
}]);
then in your controller do this:
PickerService.getFiles().then(function(returnValues){ // the promise is executed here as the return values are here
$scope.myDirectiveData = returnValues.data;
});
this does not have scope anymore where you are trying to use it do this instead:
app.service('UserService', [$http', function($http) {
var users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
console.log(users);
}
};
promise.then(function() {
console.log(users);
});
}]);
all local variables to a service should just be vars if you assign them to this as a property than they will be included every time the service is injected into a controller which is bad practice.
I think what your asking for is a solution along the lines of defining your service like this:
angular.module('app')
.service('User', function($http, $q) {
var users = null;
var deferred = $q.defer()
return {
getUsers: function() {
if(users) {
deferred.resolve(users);
} else {
$http.get('users.json');
.success(function(result) {
deferred.resolve(result);
})
.error(function(error) {
deferred.reject(error);
});
}
return deferred.promise;
}
};
});
Then in one Each controller you would have to do this:
angular.module('app')
.controller('ACtrl', function($scope, User) {
User.getUsers().then(function(users) {
// Same object that's in BCtrl
$scope.users = users;
});
});
angular.module('app')
.controller('BCtrl', function($scope, User) {
User.getUsers().then(function(users) {
// Same object that's in ACtrl
$scope.users = users;
});
});
NOTE: Because the deferred.promise the same promise passed to all controllers, executing deferred.resolve(users) in the future will cause all then success callbacks in each of your controllers to be called essentially overwriting the old users list.
All operations on the list will be noticed in all controllers because the users array is a shared object at that point. This will only handle updates to the user list/each individual user on the client side of your application. If you want to persist changes to the server, you're going to have to add other $http methods to your service to handle CRUD operations on a user. This can generally be tricky and I highly advise that you check out ngResource, which takes care of basic RESTful operations
Related
I'm trying to do an ajax call via $http service inside a custom service. Then I want to customize, inside my controller, the data received in the custom service.
I wrap the customizing data function within $interval inside the controller: by this way I can customize my data when it is received.
The problem is: while the data is correctly logged in the service, the service seems like it doesn't return anything, although it should have returned (return response.data.Items)
So $interval loops indefinitely, and I can't customize my data.
var myApp = angular.module('MyApp', []);
myApp.service('ajaxcall', ['$http', function($http) {
this.getjson = function () {
$http.get("http://localhost:3000/one")
.then(function(response) {
console.log(response.data.Items); //data logged correctly
return response.data.Items;
});
}
}])
.controller('MyCtrl', ['$scope', '$interval', 'ajaxcall', function($scope, $interval, ajaxcall) {
var new_items = ajaxcall.getjson();
customize_data = $interval(function () { //loops indefintely, new_items=undefined, why?
if (new_items) {
// customize data
}
}, 200);
for(var i=0; i<new_items.length; i++) {
$scope.items.push(new_items[i]);
}
}]);
You could say: just move the customize data function in the custom service. First at all I don't want to do it. Secondly it doesn't even make sense: the $scope is not available in a service, so in any case I should wait for the $http reply.
There were several things which I wanted to point out there.
Do return $http promise from this.getjson method, so that you can chain that promise while getting data from it.
this.getjson = function() {
//returned promise here
return $http.get("http://localhost:3000/one")
.then(function(response) {
console.log(response.data.Items); //data logged correctly
return response.data.Items;
});
}
var new_items = ajaxcall.getjson() line doesn't stored the data returned by getjson call, it will have undefined value as your currently getting. After finish up above change, new_items will hold promise return by ajaxcall.getjson. Thereafter use $q.when to keep eye on promise to get resolved & check for data inside its .then function.
customize_data = $interval(function() { //loops indefintely, new_items=undefined, why?
$q.when(new_items).then(function(res) {
if (customizeData) {
//Do Logic
}
})
}, 200);
Side Note: You could face problem with this code, as you had 200ms time for each interval . Which can make multiple ajax calls before completing the last call(which would be kind of unexpected behaviour). To
resolve such issue you could use $interval.cancel(customize_data);
//once desired interval work has done
if you want to get the return data, you would write a factory instead of service!
code:
myApp.factory('ajaxcall', ['$http', function($http) {
return {
getjson : function () {
$http.get("http://localhost:3000/one")
.then(function(response) {
console.log(response.data.Items); //data logged correctly
return response.data.Items;
});
}
}
}])
You are misusing the promise returned by your asynchronous call. Here is what you need to do in your controller to change the data:
ajaxcall.getjson()
.then(function(new_items) {
// customize data
// this part should go here as well
for(var i=0; i<new_items.length; i++) {
$scope.items.push(new_items[i]);
}
});
No need to use intervals or timeouts. Pay attention, ajaxcall.getjson() does NOT return your data, it returns the promise resolved with your items.
Read about the promises in angular.
use promise when your making http calls from service
myApp.service('ajaxcall', ['$http', '$q', function($http, $q) {
this.getjson = function() {
var q = $q.defer();
$http.get("http://localhost:3000/one")
.success(function(data) {
console.log(data); //data logged correctly
q.resolve(data);
})
.error(function(err) {
q.reject(err);
});
return q.promise;
}
}]);
changes in controller to wait for promise
ajaxcall.getjson()
.then(function(data){
console.log(data);
});
I don't know if this is even one of the Angular concepts or possible to do but i have a service that call the user information (name, id, age, ...):
.factory('me', function($resource, API_URL, $q) {
return {
getUser: function() {
var deferred = $q.defer();
var url = API_URL + 'api/me';
$resource(url)
.get(function(user) {
deferred.resolve(user);
}, function(response) {
deferred.reject(response);
});
return deferred.promise;
}
};
})
I use this service in many controllers to get the user data and use it to send with other http calls. for example in my newItemCtrl
.controller('newItemCtrl', function($scope, $http, API_URL, me) {
i called the me service as i did in many other controllers
and am wondering is there a way to call this service only once and use it in all the controllers instead of x times in each controller
I do something similar all the time with services by setting the value to the service once it's returned the first time. Now once the data is set in the service it will return the stored user data instead of making a request to your server.
service('myService', function($q, $http) {
this.data;
var self = this;
this.getMyData = function() {
if (angular.isDefined(self.data)) {
// use $q to return a promise
return $q.when(self.data)
}
return $http.get('myurl').then(function(resp) {
self.data = resp;
})
}
}
In your controller you can call myService.getMyData()
//Assume Value1 and Service1 have been correctly injected
if (Value1.data === null) {
Your_Var = Service1.call;
} else {
Your_Var = Value1.data;
}
For simplicity sake I would put that in a ternary expression when implementing.
For DRY compliance.
Going with DRY we need a SharedFactoryFuncion to handle $http requests
Your_Var = Service1.call;
//Inside Service1.call
return Value1.data === undefined ? SharedFactoryFuncion.get("url") : Value1.data;
There are numerous ways to handle the return of data from .get() so I won't go there as it's not pertinent.
I am new to Angular, so if you ask the question: "Why don't you...?" The answer is...because I didn't know I could.
Have a factory make an API call, then inject that factory into a parent controller that will have scope over the entire page. Then have child controllers nested and inherit from the parent controller.
Here is what I have so far. I may be WAY off here, and if that is the case, please tell me. I am working on this alone, and have no help, so any help is welcomed.
Thank you.
var app = angular.module('myApp', []);
app.factory('myFactory', function($http){
var MyFactory = function(){};
MyFactory.getParams = function(){
return $http.get('/getparameters');
.success(function(data){
var roomname = data.roomname;
})
MyFactory.getRoom(roomname);
};
MyFactory.getRoom = function(room){
return $http.get('/my/api/' + room);
};
});
app.controller('RoomCtrl', function($scope, myFactory){
$scope.data = myFactory;
});
You don't need to use resource, you need use promise;
EDITED
I tried to make more clear for you
app.factory('apicall', ['$q','$http',function($q, $http){
function getRoom(options){
var promise = $http.get(options.paramUrl)
.success(function(data){
//handle your error
return $http.get(options.roomUrl + data.roomname);
})
.error(function(msg){
return $q.when(msg)
})
return promise;
}
return {getRoom:getRoom};
}])
if you want call your factory in where you want
app.controller('RoomCtrl', ['$scope','apicall',function($scope, apicall){
var options = {
paramUrl:'/getparameters',
roomUrl:'/my/api/'
}
apicall.getRoom.all(function(result){
$scope.data = result;
})
}]);
The $q service helps you to handle combination of two asynchronous calls
app.factory('apicall', function($q, $http) {
var deferred = $q.defer();
$http.get('/getparameters')
.success(
function(data) {
var roomname = data.roomname;
$http.get(baseURL + 'room/' + roomname)
.success(
function(roomData) {
deferred.resolve(roomData);
}
)
.error(
function() {
deferred.reject();
}
)
})
.error(
function() {
deferred.reject();
}
);
return deferred.promise;
});
And here is controller where you can use your service
app.controller('someCtrl', function(apicall) {
apicall.then(
function(roomData) {
//success
},
function() {
//error
}
)
})
I usually separate different API calls in different factories that return a $resource.
For example, let's say we have 2 different API calls that point to different resources:
yourwebsite.com/user/:userId - returns data about the user
yourwebsite.com/photo/:photoId - return data about some photo
In angular, you would split these in 2 different factories:
"use strict";
angular.module("App.services").
factory("User",["$resource", function($resource){
return $resource("yourwebsite.com/user/:userId"),{userId:"#userId"});
}]);
and second
angular.module("App.services").
factory("Photo",["$resource", function($resource){
return $resource("yourwebsite.com/photo/:photoId"),{photoId:"#photoId"});
}]);
In your controller, you would use them like so:
angular.module("App.controllers").controller("TestController",["User","Photo", function(User, Photo){
User.get({
id:10
}).$promise.then(function(data){
console.log(data); //returns the data for user with the id=10
});
Photo.get({
id:123
}).$promise.then(function(data){
console.log(data);
});
}]);
Usually $resource maps a CRUD API(what I posted above is a basic example of a GET call).Check out the documentation on $resource - It already has the basic GET, PUT, POST, DELETE functions.
I would recommend using $http if you have only 1 simple operation on that URL and not all 4 of them. You can then inject $http in your controller and do the request there instead of creating a factory for it.
If you have 2 requests and they are chained(second one depends on the data received from the first one) you have to wait until the first one is resolved. With $resource you can do this in the following way:
angular.module("App.controllers").controller("TestController",["User","Photo", function(User, Photo){
var user_promise = User.get({
id:10
}).$promise.then(function(data){
console.log(data); //returns the data for user with the id=10
return data;
});
var photo_promise = Photo.get({
id:123
}).$promise.then(function(data){
console.log(data);
return data;
});
user_promise.then(photo_promise).
catch(function(error){
console.log(error); //catch all errors from the whole promise chain
});
}]);
I have a basic data Service which will be used across Controllers. But I'm having an issue grabbing some data that's been added via $http.
Service:
angular.module('core').service('FormService', ['$http', function($http) {
var _this = this;
_this.dropdownData = {
contactTimes: ['Anytime','Morning','Afternoon','Evening'],
industries: {},
};
$http.get('/json').success(function(resp){
_this.dropdownData.industries = resp.industries;
});
}]);
Controller:
angular.module('core').controller('SignupController', ['$scope', '$http', '$state', 'FormService', function($scope, $http, $state, FormService) {
console.log(FormService.dropdownData); // Shows full object incl industries
console.log(FormService.dropdownData.industries); // empty object {}
}]);
How do I get FormService.dropdownData.industries in my controller?
Create a service like below
appService.factory('Service', function ($http) {
return {
getIndustries: function () {
return $http.get('/json').then(function (response) {
return response.data;
});
}
}
});
Call in controller
appCtrl.controller('personalMsgCtrl', ['$scope', 'Service', function ($scope, Service) {
$scope.Industries = Service.getIndustries();
}]);
Hope this will help
Add a method to your service and use $Http.get inside that like below
_this.getindustries = function (callback) {
return $http.get('/json').success(function(resp){
_this.dropdownData.industries = resp.industries;
callback(_this.dropdownData)
});
};
In your controller need to access it like below.
angular.module('core').controller('myController', ['$scope', 'FormService', function ($scope, FormService) {
FormService.getDropdownData(function (dropdownData) {
console.log(dropdownData); // Shows full object incl industries
console.log(dropdownData.industries); // object {}
});
} ]);
Given that your console log shows the correct object, that shows your service is functioning properly. Only one small mistake you have made here. You need to access the data attributes in your return promise.
angular.module('core').service('FormService', ['$http', function($http) {
var _this = this;
_this.dropdownData = {
contactTimes: ['Anytime','Morning','Afternoon','Evening'],
industries: {},
};
$http.get('/json').success(function(resp){
//note that this is resp.data.industries, NOT resp.industries
_this.dropdownData.industries = resp.data.industries;
});
}]);
Assuming that you're data is indeed existing and there are no problems with the server, there are quite a few possible solutions
Returning a promise
angular.module('core').service('FormService', ['$http', function($http) {
var _this = this;
_this.dropdownData = {
contactTimes: ['Anytime','Morning','Afternoon','Evening'],
industries: {},
};
_this.dropdownData.industries = $http.get('/json');
}]);
//Controller
FormService.industries
.then(function(res){
$scope.industries = res.industries
});
Resolving with routeProvider / ui-route
See: $http request before AngularJS app initialises?
You could also write a function to initialize the service when the application starts running. At the end of the day, it is about waiting for the data to be loaded by using a promise. If you never heard about promises before, inform yourself first.
The industries object will be populated at a later point in time when the $http call returns. In the meantime you can still bind to the reference in your view because you've preserved the reference using angular.copy. When the $http call returns, the view will automatically be updated.
It is also a good idea to allow users of your service to handle the event when the $http call returns. You can do this by saving the $promise object as a property of industries:
angular.module('core').service('FormService', ['$http', function($http) {
var _this = this;
_this.dropdownData = {
contactTimes: ['Anytime','Morning','Afternoon','Evening'],
industries: {},
};
_this.dropdownData.industries.$promise = $http.get('/json').then(function(resp){
// when the ansyc call returns, populate the object,
// but preserve the reference
angular.copy( resp.data.industries, _this.dropdownData.industries);
return _this.dropdownData.industries;
});
}]);
Controller
app.controller('ctrl', function($scope, FormService){
// you can bind this to the view, even though the $http call has not returned yet
// the view will update automatically since the reference was preserved
$scope.dropdownData = FormService.dropdownData;
// alternatively, you can hook into the $http call back through the $promise
FormService.dropdownData.industries.$promise.success(function(industries) {
console.log(industries);
});
});
I make an $http call inside a service that is supposed to get data from my server. For some reason I can't get my service to work - nothing happens. I know the server code works because if I place the $http call inside a function within my controller, then it gets the server data as expected. Here is the code I have so far:
app.service('myService', function($q,$compile,$http) {
this.getData = function() {
var deferred = $q.defer();
var promise = $http.get('myfile.php').success(function (data) {
var response = $compile(data)($scope);
deferred.resolve(response);
});
return deferred.promise;
};
});
Also, I know the code that uses this service works because if I do something like the following,
app.service('myService', function() {
this.getData = function() {
return 'TEST';
};
});
then I will see the word "TEST" show up in the div that utilizes this service. I feel like I'm very close, but there is something I am missing. Any ideas on what I'm doing wrong?
UPDATE:
controller: function($scope, $http, $rootScope, myService){
var scope = $rootScope;
scope.viewMyData = function() {
var element = myService.getData();
$('#myDiv').html(element);
}
}
HTML:
<div ng-click="viewMyData()">Click Here</div>
<div id="myDiv"></div>
If I strip the code in myService and simply return TEST (as above), then I will see "TEST" show up in id="myDiv". But for some reason the $http call isn't being triggered.
#tymeJV is right, but here's my attempt to spell out the example better. $http returns a promise interface that allows you to chain callbacks to be executed when the $http response returns. So, in this case, calling myService.getData() can't return the result immediately (it's off getting the data from the server), so you need to give it a function to execute when the server finally responds. So, with promises, you simply attach your callback using the thePromise.then(myCallbackFunc).
Service
app.service('myService', function($q,$compile,$http) {
this.getData = function() {
var promise = $http.get('myfile.php');
promise = promise.then(function (response) {
return response.data;
});
return promise;
};
});
Controller
controller: function($scope, $rootScope, myService){
var scope = $rootScope;
scope.viewMyData = function() {
myService.getData().then(function(data) {
$('#myDiv').html(element);
});
}
}
Use .then in the controller to continue the promise pattern:
myService.getData().then(function(data) {
$('#myDiv').html(data);
});