I got this strange problem. For some bizzare reason, my setInterval won't work without $http.get() inside it. Currently, $http is set as a dependency for controller hosting said interval. But if I get rid of them (both dependency and call itself) setInterval stops working. I have no idea how to fix this.
Here is code for the controller
main.controller('timeCtrl', function($scope, $http, clockService) {
$scope.time = clockService.timeBase();
setInterval(function() {
$http.get();
$scope.time = clockService.timeBase();
}, 500);
});
After removing the dependancy, it looks like this
main.controller('timeCtrl', function($scope, clockService) {
$scope.time = clockService.timeBase();
setInterval(function() {
$scope.time = clockService.timeBase();
}, 500);
});
But it doesn't work. What the heck is wrong?
setInterval is JS function and it won't trigger digest cycle so angular won't see that something have changed.
try using it's angular version - $interval
P.S. when a promise is resolved it triggers digest cycle so that call to $http did the job, but it's obviously not the way to do what you want
Related
Using $resource to get the data, the pages have the data from the previous page until I refresh it. I've read that it's because angular itself doesn't know that it's been updated. However even after using $watch and $applyAsync, it doesn't work. ($apply gives me a digest error).
angular.module('app.factory',[])
.factory('teamService', ['$resource','$routeParams',function($resource, $routeParams){
return $resource('/api/team/:id', {id: $routeParams.id},{
update: {
method: 'PUT'
}
});
}]);
(function(){
angular.module('app.team',[])
.controller('TeamController',['teamService','$scope','$rootScope','$resource',function(teamService,$scope,$rootScope, $resource){
$scope.teamid = teamService.query();
Your code looks good to me, so I think this is a routing issue. Maybe you could try resolving the resource in the router and not in the controller. https://github.com/angular-ui/ui-router/wiki#resolve
I would also try assigning when the promise resolves:
teamService.query().$promise.then(function (result) {
$scope.teamid = result;
}
And to force a digest you could try $timeout instead of $apply. ($timeout needs to be injected into the controller.) $apply gives an error if called during a digest, $timeout waits until the current digest has completed (it still uses $apply internally).
teamService.query().$promise.then(function (result) {
$timeout(function(){
$scope.teamid = result;
});
});
Sorry, not a real answer - the comment box is so limited at times.
Im working on angularjs 1.4. Im trying to have some frontend-cache collection that updates the view when new data is inserted. I have checked other answers from here Angularjs watch service object but I believe Im not overwriting the array, meaning that the reference is the same.
The code is quite simple:
(function(){
var appCtrl = function($scope, $timeout, SessionSvc){
$scope.sessions = {};
$scope.sessions.list = SessionSvc._cache;
// Simulate putting data asynchronously
setTimeout(function(){
console.log('something more triggered');
SessionSvc._cache.push({domain: "something more"});
}, 2000);
// Watch when service has been updated
$scope.$watch(function(){
console.log('Watching...');
return SessionSvc._cache;
}, function(){
console.log('Modified');
}, true);
};
var SessionSvc = function(){
this._cache = [{domain: 'something'}];
};
angular.module('AppModule', [])
.service('SessionSvc', SessionSvc)
.controller('appCtrl', appCtrl);
})();
I thought that the dirty checking would have to catch the changes without using any watcher. Still I put the watcher to check if anything gets executed once the setTimeout function is triggered. I just dont see that the change is detected.
Here is the jsbin. Im really not understanding sth or doing a really rockie mistake.
You need to put $scope.$apply(); at the bottom of your timeout to trigger an update. Alternatively you can use the injectable $timeout service instead of setTimeout and $apply will automatically get called.
jsbin
I have an angular controller:
.controller('DashCtrl', function($scope, Auth) {
$scope.login = function() {
Auth.login().then(function(result) {
$scope.userInfo = result;
});
};
});
Which is using a service I created:
.service('Auth', function($window) {
var authContext = $window.Microsoft.ADAL.AuthenticationContext(...);
this.login = function() {
return authContext.acquireTokenAsync(...)
.then(function(authResult) {
return authResult.userInfo;
});
};
});
The Auth service is using a Cordova plugin which would be outside of the angular world. I guess I am not clear when you need to use a $scope.$apply to update your $scope and when you don't. My incorrect assumption was since I had wrapped the logic into an angular service then I wouldn't need it in this instance, but nothing gets updated unless I wrap the $scope.userInfo = statement in a $timeout or $scope.$apply.
Why is it necessary in this case?
From angular's wiki:
AngularJS provides wrappers for common native JS async behaviors:
...
jQuery.ajax() => $http
This is just a traditional async function with a $scope.$apply()
called at the end, to tell AngularJS that an asynchronous event just
occurred.
So i guess since your Auth service does not use angular's $http, $scope.$apply() isn't called by angular after executing the Async Auth function.
Whenever possible, use AngularJS services instead of native. If you're
creating an AngularJS service (such as for sockets) it should have a
$scope.$apply() anywhere it fires a callback.
EDIT:
In your case, you should trigger the digest cycle once the model is updated by wrapping (as you did):
Auth.login().then(function(result) {
$scope.$apply(function(){
$scope.userInfo = result;
});
});
Or
Auth.login().then(function(result) {
$scope.userInfo = result;
$scope.$apply();
});
Angular does not know that $scope.userInfo was modified, so the digest cycle needs to be executed via the use of $scope.$apply to apply the changes to $scope.
Yes, $timeout will also trigger the digest cycle. It is simply the Angular version of setTimeout that will execute $scope.$apply after the wrapped code has been run.
In your case, $scope.$apply() would suffice.
NB: $timeout also has exception handling and returns a promise.
I have a custom directive datepicker that uses scope.apply and works well. I cut out most of it to avoid cluttering the question, here is a simple version
appAdmin.directive("datepickerPss", ["$compile", "$parse", function ($compile, $parse) {
return {
$element.datepicker($scope.options).on("changeDate", function (ev) {
$scope.$apply(function () {
ngModel.$setViewValue(ev.date);
});
});
}
}]);
I have the custom datepicker in a modal, I simply want to initialize the value so in my controller I did the following at the top and had the "$digest already in progress" error
$scope.sDate = Date.now();
So reading up on this issue and the scope apply I changed it to the following in my controller
$timeout(function() {
$scope.sDate = Date.now();
});
However I still get the $digest in progress error. I'm not sure where to go from here. All the posts I have read have had their issues resolved by using $timeout.
Remove $scope.$apply and just use $timeout instead.
$element.datepicker($scope.options).on("changeDate", function (ev) {
$timeout(function () {
ngModel.$setViewValue(ev.date);
}, 0);
});
$scope.$apply starts a new $digest cycle on $rootScope, so calling it inside of your directive starts another $digest cycle while one is already occurring. By wrapping your call in $timeout, it'll wait until the previous $digest cycle finishes before applying your changes.
In addition, if you are trying to initialize a value AFTER you've already bound bound your change event in your directive, you could run into issues since the directive's digest cycle might still be in progress while your controller is being parsed and executed.
Please consider the following angularjs code for a controller:
(function (app) {
var controller = function ($scope, $state, datacontext) {
$scope.$parent.manageTitle = "Account Management";
$scope.accounts = [];
var init = function () {
getRecords();
};
var getRecords = function () {
return datacontext.getAccounts().then(function (data) {
$scope.$apply(function () {
$scope.accounts = data;
});
});
};
init();
};
app.controller("accountsCtrl", ["$scope", "$state", "datacontext", controller]);
})(angular.module("app"));
Removing the $scope.$apply wrapper and leaving just the "$scope.accounts = data" in the getRecords method breaks the code. The data is retrieved but the ng-repeat directive in the html is not automatically updated. I'm trying to get my arms around the entire $apply/$digest model, but it sure seems to be that the $apply should NOT be required in this case.
Am I doing something wrong?
Thanks.
<------------------------------------------ EDIT ---------------------------------------->
Ok, thanks for the responses. Here is the datacontext. It uses Breeze. I still can't figure out what the problem is - - I just don't see why $apply is required in the code, above.
(function (app) {
var datacontext = function () {
'use strict';
breeze.config.initializeAdapterInstance('modelLibrary', 'backingStore', true);
breeze.config.initializeAdapterInstance("ajax", "angular", true);
breeze.NamingConvention.camelCase.setAsDefault();
var service;
var manager = new breeze.EntityManager('api/ProximityApi');
var entityQuery = breeze.EntityQuery;
var queryFailed = function (error) {
};
var querySuccess = function (data) {
return data.results;
};
var getAccounts = function () {
var orderBy = 'accountName';
return entityQuery.from('Accounts')
.select('id, accountName')
.orderBy(orderBy)
.using(manager)
.execute()
.then(querySuccess, queryFailed);
};
service = {
getAccounts: getAccounts
};
return service;
};
app.factory('datacontext', [datacontext]);
})(angular.module('app'));
Thanks again!
Thanks for your answers. Jared - you're right on the money. By default, Breeze does not use angular $q promises, but uses third-party Q.js promises instead. Therefore, I needed $apply to synchronize the VM to the view. Recently however, the Breeze folks created angular.breeze.js, which allows the Breeze code to use angular promises, instead. By including the angular.breeze module in the application, all Breeze code will use native angular promises and $http instead.
This solved my problem and I could remove the $apply call.
See: http://www.breezejs.com/documentation/breeze-angular-service
The reason that you need to use the $apply function is the result of using Breeze to to return the data. the $apply function is used to get angular to run a digest on all the internal watches and update the scope accordingly. This is not needed when all changes occur in the angular scope as it does this digest automatically. In your code, because you are using Breeze the changes are taking place outside the angular scope, thus you will need to get angular to manually run the digest, and this is true for anything that takes place out side of angular (jQuery, other frameworks ect...). It is true that Breeze is using promises to update the data, however Angular does not know how to handle the changes after the promise returns because it is out side the scope. If you were using an angular service with promises then the view would be updated automatically. If your code is working correctly as is then it would be the correct way to use $apply in this way.
The only thing I might suggest is to change the way you are calling the apply to make sure that it will only run if another digest is not currently in progress as this can cause digest errors. I suggest you call the function as such:
if(!$scope.$$phase){$scope.$apply(function () {
$scope.accounts = data;
});
Or the other option would be to write a custom wrapper around the $apply function like this SafeApply