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.
Related
I'm trying the following code. Everything seems to be working as I see the component appear/disappear depending on the value I set for the variable. However, when I do that in the setTimeout(...) function, it starts to misbehave. The poofy text shows but the value set to vm doesn't. My guess is that I need to pass it in somehow but I'm not sure how.
(function () {
'use strict';
angular
.module("app")
.controller("Banana", banana);
function banana() {
var vm = this;
vm.showStuff = false;
setTimeout(function () {
console.log("poof!");
vm.showStuff = true;
}, 1000);
}
})();
Do I need to make the view-model globally accessible?
Use the $apply method with setTimeout:
//WORKS
setTimeout(function () {
$scope.$apply("vm.showStuff = true");
}, 1000);
OR use the AngularJS $timeout service:
//RECOMMENDED
$timeout(function () {
vm.showStuff = true;
}, 1000);
The window.setTimeout method creates an event outside the AngularJS framework and its digest cycle. The $timeout service wraps window.setTimeout and integrates it with the AngularJS framework and its digest cycle.
Angular modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and Angular execution context. Only operations which are applied in Angular execution context will benefit from Angular data-binding, exception handling, property watching, etc... You use $apply() to enter Angular execution context from JavaScript.
Keep in mind that in most places (controllers, services) $apply has already been called for you by the directive which is handling the event. An explicit call to $apply is needed only when implementing custom event callbacks, or when working with third-party library callbacks.
— AngularJS Developer Guide - Integration with the browser event loop
Try use the script bellow.
(function () {
'use strict';
angular
.module("app")
.controller("Banana", banana);
function banana($timeout) {
var vm = this;
vm.showStuff = false;
$timeout(function () {
console.log("poof!");
vm.showStuff = true;
}, 1000);
}
})();
To be noted - there's additional step required.
Substitute setTimeout(...) for $timeout(...).
Pass $timeout into banana(...).
Provide banana.$inject = ["$timeout",...].
Hey What Mateus Koppe is showing it's a good answer, I would like to extend, just because this can help someone that needs to apply changes to the view.
The $Timeout service on angular refreshes the data on the view, but with setTimeout you don't have the same effect, so you should use $scope and $apply() to force the refresh on the screen.
On my EXAMPLE I use them, to show you the effect.
//Timeout angularjs
$timeout(function () {
console.log("poof! $timeout");
vm.showStuff = true;
}, 1000);
//Tiemout regular
setTimeout(function () {
console.log("poof1! setTimeout");
vm.showStuff1 = true;
}, 1000);
//Timeout + $scope.$apply
setTimeout(function () {
console.log("poof2! setTimeout");
vm.showStuff2 = true;
$scope.$apply(); //<<<<<<<<<<<<<<<<<<<<<<<<<<<
}, 3000);
I hope it helps.
As its explained on $apply()
$apply() is used to execute an expression in angular from outside of
the angular framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the angular framework we need to perform proper scope life-cycle of
exception handling, executing watches.
Also you should avoid using on $digest()
Usually, you don't call $digest() directly in controllers or in
directives. Instead, you should call $apply() (typically from within a
directive), which will force a $digest().
Please check my example here.
https://jsfiddle.net/moplin/x8mnwf5a/
As explained in previous answers it has to do with the $digest cycle, native setTimeout() doesn't trigger the digest cycle and therefore the view doesn't get re-rendered with new value of vm.showStuff.
I'd slightly extend Pablo Palacious's answer with the following usage of $scope.$apply(),
//Timeout + $scope.$apply
setTimeout(function () {
$scope.$apply(function () {
console.log("poof2! setTimeout");
vm.showStuff2 = true;
});
}, 3000);
You could also consider using $scope.$applyAsync() which queues up expressions and functions for execution in the next digest cycle, as described here,
$applyAsync([exp]);
Schedule the invocation of $apply to occur at a later time. The actual time difference varies across browsers, but is typically around ~10 milliseconds.
This can be used to queue up multiple expressions which need to be evaluated in the same digest.
Really have no idea why this doesn't work. I must be doing something incredibly stupid.
Here is a controller:
angular.module('nightlifeApp')
.controller('TestCtrl', function($scope) {
$scope.testvar = 'before';
setTimeout(function() {
$scope.testvar = 'after';
}, 2000);
});
and here is the view that has this as the controller:
h1(ng-bind='testvar')
h1 {{testvar}}
But neither h1 element ever changes! Any thoughts?
If you're using setTimeout then you manually need to trigger apply. Like
$apply() is used to execute an expression in angular from outside of
the angular framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the angular framework we need to perform proper scope life cycle of
exception handling, executing watches.
setTimeout(function() {
$scope.$apply(function() {
$scope.testvar = 'after';
});
}, 2000);
In my opinion you should use $timeout service. So it will trigger $apply() automatically. Your code will look like
$timeout(function () {
$scope.testvar = 'after';
}, 2000);
Make sure you've injected $timeout service in your controller. In your HTML you don't need to use ng-bind. You're doing same in controller. Only
<h1> {{testvar}} </h1>
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
I am doing so:
angular.element('body').injector().get("myService").somevar = true
And somewhere else I'm grabbing it:
$scope.$watch( function () { return myService.somevar; }, function (somevar) {
console.log(somevar)
});
But the watcher does not trigger, even though if i check the value through the console it has in fact changed
As already mentioned, you need to use $apply:
$apply() is used to execute an expression in angular from outside of
the angular framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the angular framework we need to perform proper scope life cycle of
exception handling, executing watches.
For example:
var element = angular.element(document.querySelector('body'));
var service = element.injector().get('myService');
var scope = element.scope();
scope.$apply(function () {
service.somevar = true;
});
Demo: http://plnkr.co/edit/Iy3CiRzDdxFTwVq68JWi?p=preview
I'm trying to update the ui by pushing a new entry into an array but for some reason the ui is not updated until the next operation on the array.
function TestCtrl($scope){
$scope.projects = [{name: "project1"}];
$scope.test = function(){ return "batman"; };
$scope.addNew = function(){
$scope.projects.push({name: "project2"});
setTimeout(function(){
$scope.projects.push({name: "project3"});
}, 1000);
};
}
And here is an example http://jsbin.com/itasis/4/edit
I didn't tested yet but I expect the same issue in behavior from an ajax request.
Use $timeout instead of setTimeout. $timeout automatically calls $apply() for us, triggering an Angular digest cycle, which will update any views that need to be refreshed.
Regarding AJAX, I would encourage you to use Angular's $http service, which will also call $apply() for us. Otherwise, in your AJAX callback, after updating your Angular models/scope, manually call scope.$apply().