ng-bind not updating, only after refreshing the page - angularjs

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.

Related

Why is $location.path not working until I click on the screen

I have the following code that aims to redirect to the './mainlandingpage' after a successful authentication. Here is the code from the controller:
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
// pass token to use for getting credentials later
sessionStorage.Token = result.getIdToken().getJwtToken();
$scope.messageText = globalData.LoggingIn;
$location.path('/mainlandingpage');
console.log(' after location');
},
onFailure: function(err) {
alert(err);
}
The console from Firebug shows the following:
POST https://cognito-idp.ap-northeast-1.amazonaws.com/200 OK 704ms
POST https://cognito-idp.ap-northeast-1.amazonaws.com/200 OK 401ms
after location
So, I know that the code went through the $location.path line but my problem is nothing happens to the page until I click something on the page (doesn't need to be a button). Is this normal behavior?
You are modifying $location path from outside Angular world(i.e. custom event authenticateUser's onSuccess event). That's why the changes aren't being reflecting in instantly in the URL.
Changing angular bindings from outside world doesn't update current changes directly on the page unless it runs through digest cycle. To make those changes happen, you have to call digest cycle manually to tell Angular digest system that some changes have been happened in application context. Since you have to kick off digest cycle to make them in sync. For kick off digest cycle you could use $timeout(most preferred)/$scope.$apply function.
//inject `$timeout` in controller function
$timeout(function(){
$location.path('/mainlandingpage');
})
cognitoUser.authenticateUser('authenticationDetails', function( $location) {
$location.path('/mainlandingpage');
//your code here.......
});
You need to declare the $location in function as shown above.try this

setInterval inside ng-controller won't work without $http

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

confused about the need for $scope.$apply

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.

How to resolve promises in AngularJS, Jasmine 2.0 when there is no $scope to force a digest?

It seems that promises do not resolve in Angular/Jasmine tests unless you force a $scope.$digest(). This is silly IMO but fine, I have that working where applicable (controllers).
The situation I'm in now is I have a service which could care less about any scopes in the application, all it does it return some data from the server but the promise doesn't seem to be resolving.
app.service('myService', function($q) {
return {
getSomething: function() {
var deferred = $q.defer();
deferred.resolve('test');
return deferred.promise;
}
}
});
describe('Method: getSomething', function() {
// In this case the expect()s are never executed
it('should get something', function(done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
expect(1).toEqual(2);
});
done();
});
// This throws an error because done() is never called.
// Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
it('should get something', function(done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
expect(1).toEqual(2);
done();
});
});
});
What is the correct way to test this functionality?
Edit: Solution for reference. Apparently you are forced to inject and digest the $rootScope even if the service is not using it.
it('should get something', function($rootScope, done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
});
$rootScope.$digest();
done();
});
You need to inject $rootScope in your test and trigger $digest on it.
there is always the $rootScope, use it
inject(function($rootScope){
myRootScope=$rootScope;
})
....
myRootScope.$digest();
So I have be struggling with this all afternoon. After reading this post, I too felt that there was something off with the answer;it turns out there is. None of the above answers give a clear explanation as to where and why to use $rootScope.$digest. So, here is what I came up with.
First off why? You need to use $rootScope.$digest whenever you are responding from a non-angular event or callback. This would include pure DOM events, jQuery events, and other 3rd party Promise libraries other than $q which is part of angular.
Secondly where? In your code, NOT your test. There is no need to inject $rootScope into your test, it is only needed in your actual angular service. That is where all of the above fail to make clear what the answer is, they show $rootScope.$digest as being called from the test.
I hope this helps the next person that comes a long that has is same issue.
Update
I deleted this post yesterday when it got voted down. Today I continued to have this problem trying to use the answers, graciously provided above. So, I standby my answer at the cost of reputation points, and as such , I am undeleting it.
This is what you need in event handlers that are non-angular, and you are using $q and trying to test with Jasmine.
something.on('ready', function(err) {
$rootScope.$apply(function(){deferred.resolve()});
});
Note that it may need to be wrapped in a $timeout in some case.
something.on('ready', function(err) {
$timeout(function(){
$rootScope.$apply(function(){deferred.resolve()});
});
});
One more note. In the original problem examples you are calling done at the wrong time. You need to call done inside of the then method (or the catch or finally), of the promise, after is resolves. You are calling it before the promise resolves, which is causing the it clause to terminate.
From the angular documentation.
https://docs.angularjs.org/api/ng/service/$q
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined();
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));

Error: $digest already in progress in angularjs when using alert

I am just getting the json data from the services in the controller.
And I am using a callback function to print the success message when it got loaded. It is working fine but it is also throwing an error which I mentioned in the question
//JSON file
{
"pc":"name"
}
// angular services
var service = angular.module('Services', ['ngResource']).
factory('Widgets', function($resource){
return $resource('/json/home.json', {}, {
query: {method:'GET', params:{}, isArray:false}
});
});
//controller
function editWidget($scope, Widgets) {
$scope.data = Widgets.query(function(data) {
alert("Success Data Loaded ---> " + JSON.stringify(data.pc));
});
}
alert, as well as confirm and prompt will pause the execution of code (blocks the thread), during which timeouts and intervals go all haywire if they should have been triggered during the pause. The $digest loop is made up of two smaller loops, which process $evalAsync queue and the $watch list. The $evalAsync queue is used to schedule work which needs to occur outside of current stack frame, but before the browser's view render. This is usually done with setTimeout(0). Your alert during this time causes the problem.
You can use $timeout to execute an alert after the digest cycle is done and this way avoids this error.
$timeout(function () {
alert('Alert text');
});
Also don't forget to inject $timeout into your directive
if(!confirm('Your message')){
return false;
}else {
return false;
}
Return false in both cases.
#TheSharpieOne is right, It's work for me.
function delayalert(messagestr){
setTimeout(function(){
alert(messagestr);
},0);
}

Resources