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);
Related
I have the next 'problem' with Angular 1.
I have this Factory that I use to get the data for the current logged user:
angular.module('myModule')
.factory('authFactory', function ($http, $rootScope, Session, api, backend_url) {
var authFactory = this;
var user = {};
authFactory.init = function(){
// This API returns the information of the current user
api.current_user.get({}).$promise.then(function(res){
user = res;
});
}
// I use this function to return the user
authFactory.user = function () {
return user;
};
}
This is a basic Controller example where I'm trying to access the information retrieved by the above factory:
angular.module('myModule.mypage')
.controller('PageCtrl', function ($scope, authFactory) {
$scope.user = authFactory.user();
authFactory.init();
angular.element(document).ready(function () {
// This will return {} because it's called
// before the factory updates user value
console.log(authFactory.user());
console.log($scope.user);
});
});
The problem is that $scope.user = myFactory.user(); is not being updated once the Factory retrieve the user value.
I think my issue is related with myFactory.user();. I'm using a function, so the value returned by the function is not updated after myFactory.user has changed, I think that's why on PageCtrl the variable $scope.user is not getting any value.
My questions are:
Which is the best approach on my controller to wait until the user info is loaded by authFactory ?
Should I use a service instead ?
Problem with your implementation is that user is being initialized when authFactory.init() is invoked using presumably asynchronous API.
I would suggest you to return promise from authFactory.user method.
angular.module('myModule')
.factory('authFactory', function ($http, $rootScope, Session, api, $q, backend_url) {
var authFactory = this;
var user = {};
authFactory.init = function () {
// This API returns the information of the current user
return api.current_user.get({}).$promise.then(function (res) {
user = res;
});
}
//Return promise from the method
authFactory.user = function () {
var deferred = $q.defer();
if (angular.isDefined(user)) {
deferred.resolve(user);
} else {
authFactory.init().then(function () {
deferred.resolve(user);
});
}
return deferred.promise;
};
});
Then modify controller
angular.module('myModule.mypage')
.controller('PageCtrl', function ($scope, authFactory) {
authFactory.user().then(function (user) {
$scope.user = user;
})
});
angular.module('myModule')
.factory('authFactory', function ($http, $rootScope, Session, api, backend_url) {
var authFactory = this;
authFactory.user = {}
// I use this function to return the user
authFactory.getUser() = function () {
return api.current_user.get({}).$promise.then(function(res){
authFactory.user = res;
});
};
}
angular.module('myModule.mypage')
.controller('PageCtrl', function ($scope, authFactory) {
authFactory.getUser().then(function() {
$scope.user = authFactory.user;
});
});
Provide us a JSFiddle, I tried to help you without any testing environment.
I have a json-file defined and I am trying to load in one of my controllers. I am using a factory to fetch the data:
.factory('myService', function($http) {
var all_data = [];
return {
getAllData: function(){
return $http.get('js/data/all_data.json').then(function(data) {
all_data = data;
return all_data ;
});
}
}
})
Later in my controller I call getAllData() in a loadData()-function:
.controller('QuizCtrl',['$scope','$state','$http','myService',function($scope,$state,$http,myService){
// $scope.myData = []; <-- this makes the app freeze and not respond anymore
$scope.loadData = function(){
myService.getAllData().then(function(all_data){
$scope.myData = all_data.data.all_data;
alert($scope.myData);
});
}
$scope.loadData();
$scope.another_var = $scope.myData;
}])
As you can see first of all I am also calling loadData(). While debugging inside the function (see alert()) I can clearly see how the json has been loaded and applied to the $scope.myData variable.
Once I try to assign the variable to another variable (see $scope.another_var) myData is 'undefined'.
What I tried was defining $scope.myData before the $scope.loadData() call (see comment in code). Unfortunately, this simple variable declaration makes my app freeze completely. I have not found the reason for this yet. Also, I am not sure if it is related to my overall problem.
So what have I missed? Why am I not able to store my "http get" result in my controller's $scope?
EDIT: So in my case, I need the data to be there before the current Controller is even used. Would it be a legit option to put all the code which is executed within the controller into the .then-chain of the promise?
It's because your HTTP request is an asyncronous function while the assignment $scope.another_var = $scope.myData; is syncronous.
Basically what's going on is that when your QuizCtrl controller is loaded, it finishes the statement $scope.another_var = $scope.myData; before it finishes the http request of getAllData(). What you've got is a race condition.
If you want to change the value of another_var move it within your async callback:
$scope.loadData = function(){
myService.getAllData().then(function(all_data){
$scope.myData = all_data.data.all_data;
alert($scope.myData);
// because now $scope.myData is available this assignment will work:
$scope.another_var = $scope.myData;
});
}
$scope.loadData();
Hope this helps.
If you need to udpate a different value based on the value that is already on scope, you could observe the value for changes and update accordingly.
here is what you could do.
var app = angular.module("sampleApp", []);
app.controller("sampleController", ["$scope", "sampleService",
function($scope, sampleService) {
sampleService.sampleMethod(1).then(function(value) {
$scope.value = value;
$scope.$digest();
}, function(error) {});
$scope.$watch(function() {
return $scope.value;
}, function(newValue, oldValue) {
//Default Value when the dependant value is not available
newValue = newValue || 0;
$scope.myNewValue = newValue * 10;
});
}
]);
app.service("sampleService", function() {
this.sampleMethod = function(value) {
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
value = value * 2;
resolve(value);
}, 1000);
});
return promise;
};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<div ng-app="sampleApp">
<div ng-controller="sampleController">
<div>Value: {{value}}</div>
<div>Cloned Value : {{myNewValue}}
</div>
</div>
</div>
You are missing a promiss $q
take this method for instance:
.factory('myService', function($http,$q) {
var all_data = [];
return {
getAllData: function () {
var d = $q.defer();
$http.get('js/data/all_data.json')
.success(function (response) {
d.resolve(response);
});
return d.promise;
}
}
})
I have a really serious problem, I'm updating, editing, deleting data, and the two-way data binding is not working.
This is one of my controllers:
'use strict';
var EventController = function($timeout, $scope, $state, EventModel) {
this.$timeout = $timeout;
this.$scope = $scope;
this.$state = $state;
this.EventModel = EventModel;
/**
* When the page is requested, retrieve all the data.
*
*/
this.retrieve();
};
EventController.prototype = {
create: function(event) {
var that = this;
this.EventModel.Model.insert(event)
.then(function() {
that.refresh();
});
},
retrieve: function() {
var that = this;
this.EventModel.Model.find()
.then(function(result) {
that.$scope.events = result;
});
},
one: function(id) {
var that = this;
this.EventModel.Model.one(id)
.then(function(result) {
that.$scope.event = result;
});
},
update: function(id, event, state) {
if (state !== undefined) {
event.is_active = state;
}
var that = this;
this.EventModel.Model.update(id, event)
.then(function() {
that.refresh();
});
},
delete: function(id) {
var check = $('[data-controller-input]:checked');
var that = this;
$.each(check, function() {
var target = $(this);
var id = target.prop('id');
that.EventModel.Model.remove(id)
.then(function() {
that.refresh();
});
});
},
clear: function() {
this.$scope.event = angular.copy(this.$scope.initial);
},
refresh: function() {
this.$state.go(this.$state.current, {}, {reload: true});
}
};
angular
.module('adminApp')
.controller('EventController',
[
'$timeout',
'$scope',
'$state',
'EventModel',
EventController
]
);
In the create, update and delete methods I need to update the HTML without refreshing the page, I already tried using, $scope.apply, $scope.digest, $timeout after the result came, but not happens in the HTML.
If I try $scope.apply and $scope.digest the error will be:
Prevent error $digest already in progress when calling $scope.$apply()
So I was trying to wrap the $scope.$apply or $digest with the $timeout, same result, nothing happens.
Thanks.
First of all, your refresh method will never update your controller.it will simply fail just because this.$state.current won't be able to resolve any url ,template or controller.
And this is the main reason you are not able to see updated data ,just check your console you might be getting Error: Cannot transition to abstract state '[object Object]' error.
Update : I have create a plnkr.as i don't have access to event model code i simply removed it and try to create the same scenario.
http://plnkr.co/edit/RsI3TgKwcjGEXcTMKoQR?p=preview
see if this can help you
I am not sure, but try using the following function which checks the current phase before executing your function. It may solve the issue.
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
Usage:
$scope.safeApply(function() {
//Your lines
});
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
});
});
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.