I cannot add items in an array using the setTimeout function - angularjs

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().

Related

How to pass vm to a setTimeout in AngularJs? Changes to scope don't update DOM view

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.

Angularjs $scope is not updated

I am developing an angular-ionic-firebase application. I am struggling on an issue where I am unable to update a $scope in my html. My must resolve {{uevent}} and update the html with the result of {{uevent}}. My html code is below.
**<ion-view view-title="{{uevent}}">**
<ion-tabs class="tabs-stable tabs-icon-top">
<ion-tab title="Buddies" icon="ion-ios-people" ng- click="showBuddies(a)">
</ion-tab>
<ion-tab title="Summary" icon="ion-navicon" ng-click="billSummary()">
</ion-tab>
</ion-tabs>
my angular code is below. CurrUser is a factory. I've defined an empty $scope.uevent which I am expecting to update using a value returned from a factory.
var eventid = CurrUser.getEventid();
$scope.uevent = " ";
function updateEvent(desc) {
$scope.uevent = desc;
console.log($scope.uevent);// I am able to see the value returned from the factory. This function is executed below in the for loop.
};
// promise that returns a value from the factory - CurrUser.
CurrUser.getEventdesc(eventid).then(function (result) {
var description = result;
var desc;
for (var itemID in description) {
var tmp = description[itemID];
desc = tmp.Description; // This contains the value that must be updated to $scope.uevent
updateEvent(desc); // Calls the function defined above to update $scope.uevent.
};
});
I've been on this since last night without any clues. Any help is greatly appreciated and as always, thanks for your time.
UPDATE #1:(07 July)
When i added a timeout of 1 sec, the view was updated to the correct value.
function updateEvent(desc) {
$timeout(function(){
$scope.$apply(function () {
$scope.uevent = desc;
})
},1000);
};
Regards,
There are 2 execution context when You work with AngularJS - Angular Context and JavaScript Context.
When you are changing the data in AngularJS Context, the digest loop begins and all data updates, but when you change data in the JavaScript Context (e.g setTimeout, setInterval and so on), Angular doesn't know about the changes so it doesn't update the data.
In JavaScript Context you must change your data in scope.$apply() method to run the digest loop manually.
So your code will look like
var eventid = CurrUser.getEventid();
$scope.uevent = " ";
function updateEvent(desc) {
$scope.$apply(function(){
$scope.uevent = desc;
});
console.log($scope.uevent);// I am able to see the value returned from the factory. This function is executed below in the for loop.
};
// promise that returns a value from the factory - CurrUser.
CurrUser.getEventdesc(eventid).then(function (result) {
var description = result;
var desc;
for (var itemID in description) {
var tmp = description[itemID];
scope.$apply(function(){
desc = tmp.Description; // This contains the value that must be updated to $scope.uevent
})
updateEvent(desc); // Calls the function defined above to update $scope.uevent.
};
});
For more.If there is an Angular JS alternative, you must use it,because it runs te digest loop automatically($timeout instead of setTimeout(), $interval instead of setInterval())
Try this; $apply is used to trigger $digest .
function updateEvent(desc) {
$timeout(function(){
$scope.uevent = desc;
$scope.$apply();
},0)
};
Where is your controller defined in the html? Or is it in your app.js. Anyways, for a start can you do a $scope.$apply in your updateEvent function?
function updateEvent(desc) {
$scope.$apply(function(){
$scope.uevent = desc;
});
console.log($scope.uevent);
};
I dont know what happens in your getEventDesc() method but if there is a non-angular ajax request or something, then it might need the $apply to trigger the digest cycle and update watchers.
Detailed explanation below.
Reference: This awesome informative post -> https://github.com/angular/angular.js/wiki/When-to-use-$scope.$apply()
AngularJS provides wrappers for common native JS async behaviors:
Events => ng-click
Timeouts => $timeout
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.
$scope.$apply() should occur as close to the async event binding as possible.
Do NOT randomly sprinkle it throughout your code. If you are doing
if (!$scope.$$phase) $scope.$apply() it's because you are not high enough in the call stack.
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.

AngularJS - Watch service changes not updating view

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

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.

Angular JS: Chaining promises and the digest cycle

NOTE: the fiddle uses an old version of Angular, and that it's not working any more because as of 1.2 the Angular template engine does not handle promises transparently.
I'm looking into chaining promises to populate my scope, and then having the scope automatically update the dom.
I'm running into problems with this though.. If I call "then" on an already resolved promise, it creates a new promise (that will call the success function asynchronously but almost immediately). I think the problem is that we've already left the digest cycle by the time the success function is called, so the dom never updates.
Here is the code:
<div ng-controller="MyCtrl">
Hello, {{name}}! <br/>
{{name2}}<br/>
<button ng-click="go()">Clickme</button><br/>
{{name3}}
</div>
var myApp = angular.module('myApp',[]);
function MyCtrl($scope, $q) {
var data = $q.defer();
setTimeout(function() {$scope.$apply(data.resolve("Some Data"))}, 2000);
var p = data.promise;
$scope.name = p.then(angular.uppercase);
$scope.name2 = p.then(function(x) { return "Hi "+x;});
$scope.go = function() {
$scope.name3 = p.then(function(x) {
// uncomment this to make it work:
//$scope.$apply();
return "Finally: "+x;
});
};
}
http://jsfiddle.net/QZM4d/
Is there some way to make this work without calling $apply every time I chain promises?
NOTE: the fiddle uses an old version of Angular, and that it's not working any more because as of 1.2 the Angular template engine does not handle promises transparently.
To quote #pkozlowski.opensource:
In AngularJS the results of promise resolution are propagated asynchronously, inside a $digest cycle. So, callbacks registered with then() will only be called upon entering a $digest cycle.
So, when the button is clicked, we are in a digest cycle. then() creates a new promise, but the results of that then() will not be propagated until the next digest cycle, which never comes (because there is no $timeout, or $http, or DOM event to trigger one). If you add another button with ng-click that does nothing, then click that, it will cause a digest cycle and you'll see the results:
<button ng-click="">Force digest by clicking me</button><br/>
Here's a fiddle that does that.
The fiddle also uses $timeout instead of setTimeout -- then $apply() isn't needed.
Hopefully it is clear when you need to use $apply. Sometimes you do need to call it manually.

Resources