I have an angular factory that makes an $http call with a get and then.
.factory('DataModel', function($http) {
I have a .get.then that works great. The value comes back, and since I originally returned a function to return the factory value, everything updates when it changes.
Now I have to make a dependent call based on the data that returned the first time.
First try: $http.get.then inside the outer $http.get.then.
The inner (dependent) call successfully gets the data, but when it updates the factory parameters only the first .get.then is picked up by the calling controller.
Next try: $scope.$watch.
angular.module('starter.services', [])
.factory('DataModel', function($scope, $http) {
If I put a $scope parameter in there I get an error:
Unknown provider: $scopeProvider <- $scope <- DataModel
So I can't seem to use the $scope.$watch method.
Third try: callbacks?
I'm afraid that if I use a callback approach I'll get the data back, but it won't update just like my nested get.then. didn't update.
Here is my full factory:
angular.module('starter.services', [])
.factory('DataModel', function($http) {
var days = {};
var todaysFlavorIndex = 32;
var todaysFlavorName = [32, 'Loading ...', "vanilla_chocolate_chip.jpg"];
var daysLeftCalendar = [];
var flavors = [];
// calendar objects
$http.get("https://jsonblob.com/api/5544b8667856ef9baaac1")
.then(function(response) {
var result = response.data;
days = result.Days;
var dateObj = new Date();
var day = dateObj.getDate();
var endOfMonthDate = new Date(new Date().getFullYear(), dateObj.getMonth(), 0).getDate();
for (var di = day; di <= endOfMonthDate; di++) {
var flavor = days[di - 1];
daysLeftCalendar.push(flavor[1]);
}
var todaysFlavorIndex = -1;
// $scope.$watch('todaysFlavorIndex', function() {
// // Http request goes here
// alert('updating !');
// });
for (var i = 0; i < days.length; i++) {
if ((days[i])[0] == day) {
todaysFlavorIndex = (days[i])[1];
}
}
// flavors
$http.get("https://jsonblob.com/api/55450c5658d3aef9baac1a")
.then(function(resp) {
flavors = resp.data.flavors;
todaysFlavorName = flavors[todaysFlavorIndex];
});
}); // end then
return {
getDays: function() {
return days;
},
getMonth: function() {
return days;
},
getFlavors: function() {
return flavors;
},
getTodaysFlavorIndex: function() {
return todaysFlavorIndex;
},
getTodaysFlavorName: function() {
return todaysFlavorName; // flavors[todaysFlavorIndex];
},
today: function() {
var dateObj = new Date();
var day = dateObj.getUTCDate();
return todaysFlavorIndex;
},
remainingFlavorIndexes: function() {
return daysLeftCalendar
}
};
})
Firstly , services has no $scope.
So injecting scope in factory will always throw you exceptions.
Secondly , try to catch callback from controller instead of factory
Try like this
angular.module('starter.services', [])
.factory('DataModel', function($http) {
return {
myFunction: function() {
return $http.get("https://jsonblob.com/api/5544b8667856ef9baaac1");
}
}
})
.controller("myCtrl", function($scope, DataModel) {
DataModel.myFunction().then(function(result) {
// success
// put your code here
}, function(e) {
// error
});
})
Thirdly, If you wanna have inner $http you can use $q
Try like this
angular.module('starter.services', [])
.factory('DataModel', function($http) {
return {
myFunction: function() {
return $http.get("https://jsonblob.com/api/5544b8667856ef9baaac1");
},
myFunction2: function() {
return $http.get("https://jsonblob.com/api/55450c5658d3aef9baac1a");
}
}
})
.controller("myCtrl", function($scope, DataModel, $q) {
$q.all([
DataModel.myFunction(),
DataModel.myFunction2()
]).then(function(data) {
console.log(data[0]); // data from myFunction
console.log(data[1]); // data from myFunction2
});
});
Related
I'm pretty much new in angular js. What I am trying to do is pass an integer argument to http get request in my controller. This is how my sample code looks like.
(function() {
angular
.module('myApp.directory', [])
.factory('NewsService', function($http)
{
return {
getallnews: function() {
return $http.get('get_all_news_feed.php?page='+pageNumber);
}
};
})
.factory('NewsFeed', function(directoryService) {
var NewsFeed = function() {
this.items = [];
this.busy = false;
this.pageNumber = 1;
};
NewsFeed.prototype.nextPage = function() {
if (this.busy) return;
this.busy = true;
NewsService.getallnews().success(function(data) {
var itemData = data;
for (var i = 0; i < itemData.length; i++) {
this.items.push(itemData[i]);
}
this.pageNumber++;
this.busy = false;
}.bind(this));
};
return NewsFeed;
})
.controller('MyController', function(NewsFeed, NewsService) {
var inst = this;
inst.news = new NewsFeed();
});
})();
I am building a news feed app. News is fetched from get_all_news_feed.php page and I want to pass a parameter pageNumber to it. This is while implementing infinte scrolling in angular.
I am getting undefined error. Any ideas?
Modify the factory method to accept pageNumber as parameter
getallnews: function(pageNumber) {
return $http.get('get_all_news_feed.php?page='+pageNumber);
}
Pass it when calling the method
NewsService.getallnews(this.pageNumber)
I'm attempting to call a service from within another service, then use the returned object to perform some operations. I keep running into a TypeError: getDefinitions is not a function error, however.
Below is my service is called, the service doing the calling, and my relevant controller code:
definitions.service.js:
'use strict';
angular.module('gameApp')
.factory('definitionsService', ['$resource',
function($resource) {
var base = '/api/definitions';
return $resource(base, {}, {
get: {method: 'GET', url: base}
});
}]);
utilities.service.js:
'use strict';
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
return {
description: description,
detail: detail,
severity: severity,
};
function description(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].description;
}
function detail(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].detail;
}
function severity(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].severity;
}
var getDefinitions = function() {
definitionsService.get().$promise.then(function(data) {
return data;
});
};
}]);
controller.js:
'use strict';
angular.module('gameApp')
.controller('AccountsController', AccountsController);
AccountsController.$inject = ['$routeParams', 'customersService', 'utilitiesService'];
function AccountsController($routeParams, playersService, utilitiesService) {
var vm = this;
var playerId = $routeParams.playerId;
var getAccounts = function() {
playersService.getAccounts({
playerId: playerId
}).$promise.then(function(accounts) {
for (var i = 0; i < accounts.length; i++) {
if (angular.isDefined(accounts[i].secCode)) {
accounts[i].code = accounts[i].secCode;
accounts[i].severity = utilitiesService.severity(accounts[i]);
accounts[i].detail = utilitiesService.detail(accounts[i]);
accounts[i].description = utilitiesService.description(accounts[i]);
}
}
vm.accounts = accounts;
});
};
var init = function() {
getAccounts();
};
init();
}
Currently your service returns before your variable gets defined. That means the definition is never reached. So it is declared, as the function executes, but is undefined. Just move your variable definition to the top.
This will only prevent the definition error. Another problem is that your getDefinitions function doesn't return anything but you're calling a property on it. One solution I can think of is using a callback, that gets executed when your data is loaded:
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var data;
reload();
var utils = {
description: description,
detail: detail,
severity: severity,
reload: reload,
loaded: null
};
return utils;
function reload() {
definitionsService.get().$promise.then(function(data) {
data = data;
if (utils.loaded && typeof utils.loaded === "function") {
utils.loaded();
}
});
}
function description(account) {
var key = angular.isDefined(data.ABC[account.code]) ? account.code : '-';
return data.IDV[key].description;
}
}]);
Then in your controller you could use the service like this:
utilitiesService.loaded(function(){
accounts[i].description = utilitiesService.description(accounts[i]);
})
old question but still relevant. To expand on Florian Gl's answer above if you have a service with multiple functions and one or more of those functions requires a "pre-service" function to be called for example to load some resource data in like configuration information move that service call to the top, outside of the nested function (in this case below I am dealing with the promise scenario in JavaScript):
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var myFirstConfigValue = '';
// call any all services here, set the variables first
configurationService.GetConfigValue('FirstConfg')
.then(function (response) {
// set the local scope variable here
myFirstConfigValue = response;
},
function() { });
function myTestFunction() {
// make an ajax call or something
// use the locally set variable here
ajaxService.functionOneTwo(myFirstConfigValue)
.then(response) {
// handle the response
},
function(err) {
// do something with the error
});
}
}]);
Key point to note here is that if you need to load in some data you do that first outside of any other functions inside your service (e.g. you want to load some JSON data).
I have the following service:
app.service('Library', ['$http', function($http) {
this.fonts = [];
this.families = [];
// ... some common CRUD functions here ...
// Returns the font list
this.getFonts = function() {
if(_.isEmpty(this.fonts)) this.updateFonts();
return this.fonts;
};
// Returns the family list
this.getFamilies = function() {
if(_.isEmpty(this.families)) this.updateFamilies();
return this.families;
};
// Update the font list
this.updateFonts = function() {
var self = this;
$http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
console.log('Library:: fonts updated', self.fonts);
});
};
// Update the family
this.updateFamilies = function() {
var self = this;
$http.get(BACKEND_URL+'/families').success(function(data) {
var sorted = _.sortBy(data, function(item) { return item });
self.families = sorted;
console.log('Library:: families updated', self.families);
});
};
}]);
And the following main controller code:
app.controller('MainController', ['$scope', '$state', 'Cart', 'Library', function($scope, $state, Cart, Library) {
console.log('-> MainController');
// Serve the right font list depending on the page
$scope.fonts = $state.is('home.cart') ? Cart.getFonts() : Library.getFonts();
$scope.families = Library.getFamilies();
}]);
The problem is, that when the view requests the content of $scope.fonts, it's still empty.
How to update $scope.fonts and $scope.families when the loading is over?
I could use $scope.$watch but I'm sure there is a cleaner way to do it...
This really is what promises were made for. Your service should return a promise that is to be resolved. You could also simplify your service:
app.service('Library', ['$http', '$q', function($http, $q) {
var self = this;
self.families = [];
// Returns the family list
self.getFamilies = function() {
var deferred = $q.defer();
if(_.isEmpty(self.families)) {
$http.get(BACKEND_URL+'/families').success(function(data) {
var sorted = _.sortBy(data, function(item) { return item });
self.families = sorted;
deferred.resolve(self.families);
console.log('Library:: families updated', self.families);
});
} else {
deferred.resolve(self.families);
}
return deferred.promise;
};
}]);
And then in your controller, use the promises then method:
app.controller('MainController', ['$scope', '$state', 'Cart', 'Library', function($scope, $state, Cart, Library) {
console.log('-> MainController');
// Serve the right font list depending on the page
$scope.fonts = $state.is('home.cart') ? Cart.getFonts() : Library.getFonts();
Library.getFamilies().then(function(result) {
$scope.families = result;
});
}]);
This is untested because of the $http, but here is a demo using $timeout:
JSFiddle
Consider passing a callback function.
Service:
this.getFonts = function(callback) {
if(_.isEmpty(this.fonts)) this.updateFonts(callback);
return this.fonts;
};
this.updateFonts = function(callback) {
var self = this;
$http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
console.log('Library:: fonts updated', self.fonts);
callback(data);
});
};
Controller:
Library.getFonts(function (data) { $scope.fonts = data; });
This could be tidied up a bit, since a callback eliminates the need for some of this code, but it'll serve as an example.
Thanks for all the answers! I ended up using a mix of callback and promise, as follow:
app.service('Library', function($http) {
// Returns the font list
this.getFonts = function(callback) {
if(_.isEmpty(self.fonts)) return self.updateFonts(callback);
else return callback(self.fonts);
};
// Update the font list
this.updateFonts = function(callback) {
return $http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
callback(data);
});
};
});
And, in the controller:
app.controller('MainController', function(Library) {
Library.getFonts(function(fonts) { $scope.fonts = fonts });
});
I tried all your suggestions, but this is the best one working with the rest of my code.
In your this.getFonts function (and your other functions), you call the data from this, which points to the function instead of the controller scope you want. Try the following instead:
var self = this;
self.fonts = [];
self.families = [];
// ... some common CRUD functions here ...
// Returns the font list
self.getFonts = function() {
if(_.isEmpty(self.fonts)) self.updateFonts();
return self.fonts; // <-- self.fonts will point to the fonts you want
};
I would try wrapping your getScope and getFonts bodies that you are calling in a
$scope.$apply(function(){ ...body here... });
Make sure you declare self = this outside any functions.
Assign the call to the value you want to store the data in and then return it.
var self = this;
self.data = [];
this.updateFonts = function() {
self.fonts = $http.get(BACKEND_URL+'/fonts').success(function(data) {
return data.data
});
return self.fonts
};
Since you're using ui-router (i saw a $state). You can use a resolve in your state and return a promise.
Doc : https://github.com/angular-ui/ui-router/wiki
Exemple :
$stateProvider.state('myState', {
resolve:{
// Example using function with returned promise.
// This is the typical use case of resolve.
// You need to inject any services that you are
// using, e.g. $http in this example
promiseObj: function($http){
// $http returns a promise for the url data
return $http({method: 'GET', url: '/someUrl'});
}
},
controller: function($scope,promiseObj){
// You can be sure that promiseObj is ready to use!
$scope.items = promiseObj.data;
}
}
In your case you'll need to turn your this.getFonts and getFamilies into promises
this.getFonts = function(){
return $http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
console.log('Library:: fonts updated', self.fonts);
});
}
There is many many way to do this, but in my opinion the resolve way is the best.
For a small Angular.js testbed project, I've set up the following plunker:
My Plunked Plunker
Originally, I had a local version of this testbed working, when the calendarViewModel was directly included in the Angular controller.
appControllers.controller('PageController', [
'$scope', '$http', 'Enums', 'ViewModels',
function ($scope, $http, Enums, ViewModels) {
var calendarViewModel = function () {
var pub = {};
pub.date = new Date();
pub.isOpen = false;
pub.today = function () {
if(pub.isOpen)
pub.date = new Date();
};
pub.clear = function () {
if(pub.isOpen)
pub.date = null;
};
pub.hide = function () {
pub.isOpen = false;
};
pub.toggle = function ($event) {
$event.preventDefault();
$event.stopPropagation();
$scope.hideCalendars();
pub.isOpen = !pub.isOpen;
};
return pub;
};
// Backing model for this 'controller'
$scope.viewModel = {
// Properties:
startCalendar: new calendarViewModel(),
endCalendar: new calendarViewModel(),
// data:
// Generates an object that is sent to the server with $http calls.
data: function () {
var object = {
startDate: startCalendar.date.toString(),
endDate: endCalendar.date.toString()
};
return JSON.stringify(object);
}
};
// - Controller-specific functions... ----------------------------------
$scope.hideCalendars = function () {
$scope.viewModel.startCalendar.hide();
$scope.viewModel.endCalendar.hide();
};
$scope.clear = function () {
$scope.viewModel.startCalendar.clear();
$scope.viewModel.endCalendar.clear();
};
$scope.today = function () {
$scope.viewModel.startCalendar.today();
$scope.viewModel.endCalendar.today();
};
// Restricts certain days from being selected.
$scope.disableWeekends = function (date, mode) {
return mode === 'day'
&& (date.getDay() === Enums.DaysOfTheWeek.Sunday
|| date.getDay() === Enums.DaysOfTheWeek.Saturday);
};
// This is a demonstration scope action. Pretty much, the pattern
// I found, is to have a view model expose a method that creates
// a stringified JSON blob that we can send to the server. This
// method is how such a save function would work.
$scope.save = function () {
var promise = $http({
method: 'POST',
url: '/some/server/url',
data: $scope.viewModel.data()
});
promise.success(function (data) {
// Do something with the returned data?
}).error(function (data) {
// Do something with the error data?
});
};
// - End of Controller-specific functions... ---------------------------
// Picker-specific options...
$scope.dateOptions = {
'starting-day': Enums.DaysOfTheWeek.Monday,
'format-day': 'dd',
'format-month': 'MM',
'format-year': 'yyyy',
'min-mode': Enums.PickerMode.Day,
'max-mode': Enums.PickerMode.Year
};
$scope.format = 'MM/dd/yyyy';
$scope.today();
}
]);
Since I refactored it out to the ViewModels constant object, though, I get the following errors from Angular:
TypeError: undefined is not a function
at Object.pub.toggle (http://run.plnkr.co/AKUBdEb5M3KT5DM9/app.services.js:31:4)
at http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:10185:21
at http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:17835:17
at Scope.$eval (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:11936:28)
at Scope.$apply (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:12036:23)
at HTMLInputElement.<anonymous> (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:17834:21)
at http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:2613:10
at forEach (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:310:20)
The reason I am doing this, is because in theory multiple controllers could have need of calendars that are tied to calendarViewModels (this is why I created the calendarViewModel functional object in the first place.) I want the calendarViewModel construct to not be tied to a specific controller, however, which I have apparently broken by refactoring it in this way.
I think I'm on the right track, but in any event, something is clearly missing. My question: what is the correct way for me to refactor my calendarViewModel, that works and is more easily reusable?
So a few things from your plunker:
Don't use app.constant to do factories. Use app.factory instead, eg:
_
appServices.factory('ViewModels', function() {
var pub = {};
pub.date = new Date();
pub.isOpen = false;
pub.today = function () {
if(pub.isOpen)
pub.date = new Date();
};
pub.clear = function () {
if(pub.isOpen)
pub.date = null;
};
pub.hide = function () {
pub.isOpen = false;
};
pub.toggle = function ($event) {
$event.preventDefault();
$event.stopPropagation();
//hideAll();
pub.isOpen = !pub.isOpen;
};
return pub;
});
Your factories are automatically shared between controllers when you do this:
_
appControllers.controller('FirstController', [ '$scope', 'MyCalendarService', function($scope, MyCalendarService){
$scope.myCalendarService = MyCalendarService;
}]);
appControllers.controller('SecondController', [ '$scope', 'MyCalendarService', function($scope, MyCalendarService){
$scope.myCalendarService = MyCalendarService;
}]);
... if the controllers are defined in parallel in the html. If they are nested, you just need to inject your service at the top level. Ideally, you just need to DI a couple of services in a controller and assign them to the scope.
Does that answer your question?
PS: hideAll is not defined in your plunker, I commented it out and things started to work.
Edit: This edited plnkr should do what you want: http://plnkr.co/edit/7VDYDQhK2CDGnwa8qhWf?p=preview
Maybe this question has already been asked, but I searched and tried most of my afternoon without any success so I really hope somebody can help me with this.
I want to be able to update my $http.get() - my data - that I have set in a factory service, every few seconds.
I added some comment to my code and also left some old stuff for you guys to see what I have tried. (the old stuff is also commented out)
My code:
ovwid.factory('recentClients', [
'$http',
'$rootScope',
function ($http, $rootScope) {
var apiURL = '/plugins/data/get_client.php';
var promise;
var recentClients =
{
async: function()
{
if ( !promise )
{
// $http returns a promise, which has a 'then' function, which also returns a promise
promise =
$http.get(apiURL)
.then(function (response) {
// The then function here is an opportunity to modify the response
// The return value gets picked up by the then in the controller.
return response.data;
});
}
// Return a promise to the controller
return promise;
}
}
return recentClients;
}]);
ovwid.controller(‘client’Ctrl, [
'$scope',
'recentClients',
'$interval',
function ($scope, recentClients, $interval) {
$scope.loading = true;
function reloadData() {
// a call to the async method
recentClients().async().then(function(data) {
// console.log(data);
$scope.loading = false;
$scope.client = data;
});
}
// Initizialize function
reloadData();
// Start Interval
var timerData =
$interval(function () {
reloadData();
}, 1000);
// function myIntervalFunction() {
// var cancelRefresh = $timeout(function myFunction() {
// reloadData();
// console.log('data refres')
// cancelRefresh = $timeout(myFunction, 5000);
// },5000);
// };
// myIntervalFunction();
// $scope.$on('$destroy', function(e) {
// $timeout.cancel(cancelRefresh);
// });
}]); // [/controller]
I see several issues.
First:
if ( !promise ) is only going to return true the first time. You are assigning it to the $http call.
Secondly:
You never access the async method in your factory.
You either need to return that from factory return recentClients.async or call it from scope recentClients.async().then(..
may be it will help
function reloadData() {
// a call to the async method
$scope.loading = true;
recentClients().then(function(data) {
// console.log(data);
$scope.loading = false;
$scope.client = data;
});
}
// Start Interval
var timerData =
$interval(function () {
if(!$scope.loading){
reloadData();
}
}, 1000);
A few things :)
recentClients().then(function(data)... will not work, in your current code it should be: recentClients.async().then(function(data)
(same remark would apply to ` and ’ qoutes that can get really tricky.
This is the syntax I use for designing services:
ovwid.factory('recentClients', ['$http', '$rootScope', function ($http, $rootScope) {
var apiURL = 'aaa.api';
var recentClients = function() {
return $http.get(apiURL)
}
return {
recentClients : recentClients
};
}]);
Full example:
(just create aaa.api file with some dummy data, fire up a server and you'll see that data is changing)
<!DOCTYPE html>
<html>
<head>
<title>Sorting stuff</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.min.js"></script>
<script>
var ovwid = angular.module("ovwid", []);
ovwid.factory('recentClients', ['$http', '$rootScope', function ($http, $rootScope) {
var apiURL = 'aaa.api';
var recentClients = function() {
return $http.get(apiURL)
}
return {
recentClients : recentClients
};
}]);
ovwid.controller('clientCtrl', [
'$scope',
'recentClients',
'$interval',
function ($scope, recentClients, $interval) {
$scope.loading = true;
function reloadData() {
// a call to the async method
recentClients.recentClients().then(function(response) {
// console.log(data);
$scope.loading = false;
$scope.client = response.data;
});
}
// Initizialize function
reloadData();
// Start Interval
var timerData =
$interval(function () {
reloadData();
}, 1000);
}]);
</script>
</head>
<body ng-app="ovwid" ng-controller="clientCtrl">
{{ client }}
</body>
</html>
You can set up a service to perform periodic server calls for you. I had found this code somewhere awhile back and refined it a bit. I wish I could remember where I got it.
angular.module('my.services').factory('timeSrv',['$timeout',function($timeout){
//-- Variables --//
var _intervals = {}, _intervalUID = 1;
//-- Methods --//
return {
setInterval : function(op,interval,$scope){
var _intervalID = _intervalUID++;
_intervals[_intervalID] = $timeout(function intervalOperation(){
op($scope || undefined);
_intervals[_intervalID] = $timeout(intervalOperation,interval);
},interval);
return _intervalID;
}, // end setInterval
clearInterval : function(id){
return $timeout.cancel(_intervals[id]);
} // end clearInterval
}; // end return
}]); // end timeSrv
And then in your controller you'd make a call like so:
$scope.getSomethingID = timeSrv.setInterval(function($scope){
[... Do stuff here - Access another service ...]
},10000,$scope);
This will execute the passed function every 10 seconds with the scope of the controller. You can cancel it at anytime by:
timeSrv.clearInterval($scope.getSomethingID);