Angular $timeout for delay - javascript

I want the browser to wait for 3 seconds before executing the next line of code.
$timeout(3000); in script is not seeming to do the trick. Am I doing anything wrong here.
I use it inside a $scope function
app.expandController = function ($scope,$interval, $timeout) {
$scope.nextLevel = function () {
//stop numbers
$scope.StopTimer();
$timeout(function(){return true;},3000);
//restart timer with new numbers
$scope.StartTimer();
$scope.thresholdwatch == false
}
}
and I split controller file by passing $scope and $timeout to another function
app.controller('myCtrl', function ($scope, $interval, $timeout) {
app.expandController($scope, $interval,$timeout);
});

If you are using $timeout you need to place your next executing code in timeout callback. But a simple hack with vanila js would be,
function sleep(ms) {
var dt = new Date();
while (Date.now() - dt.getTime() <= ms) {}
return true;
}
console.log('timer start');
sleep(3000);
console.log('Printing after 3000ms');

I think you get the idea about $timeout in AngularJS or generally JS incorrect. Both of the below examples will not work.
$timeout(function(){return true;},3000);
$timeout(3000);
$timeout is a non-blocking function so calling it will not stop JS from running the code below it. In Javascript and AngularJS, there is no truly sleep function (a NodeJS lib for sleep is actually the C++ binding for it, and therefore, not applicable to client-side app using AngularJS). Instead, to make sure that your code will be run after a specific amount of time, you put it inside the the $timeout function:
$timeout(function(){
//code need to be delayed must be in here
}, TIME_TO_WAIT_FOR);

The usage of $timeout is wrong. Use like this:
$timeout(function(){
// Do something in timeout
}, 3000);

Related

$scope.$watch doesn't trigger on every change

I'm using angularJS 1.4.8, yesterday i noticed that the $scope.$watch doesn't trigger on every change which caused bug in my application.
is there a way to force it to work on every change immediately ?
like in this code, in every change on message i want the function in watch to trigger:
(function(){
angular.module('myApp', [])
.controller('myAppController', myAppController)
function myAppController($scope){
console.log('controller loading !');
$scope.message = 'message1';
$scope.$watch('message', function(newMessage){
console.log('newMessage', newMessage)
});
function changeMessage(){
$scope.message='hi';
$scope.message='hi12';
}
changeMessage();
}
})();
the console will print:
controller loading !
newMessage hi22
plunker link https://plnkr.co/edit/SA1AcIVwr04uIUQFixAO?p=preview
edit:
I would really like to know if there are any other ways than wrapping the change with timeout and using scope apply, in my original code iv'e multiple places where i change the scope property and i would like to avoid using this every change.
This happens because the watch will only be triggered if the value is changed "between" digest loops.
Your function is changing the message value on the scope in the same function. This will be executed in the same digest loop.
When angular moves on to the next loop it will only see the last changed value which in your case will be hi22.
Here's a great article which makes this behaviour clear
update your changeMessage function so that it uses $scope.$apply function which will ensure that your changes are reflected and angular is aware of your changes to the variable.
changeMessage() {
setTimeout(function () {
$scope.$apply(function () {
$scope.message = "Timeout called!";
});
}, 2000);
}
If you change value into the same digest cycle the watcher is not triggered and last value is taken. When we run $timeout, we change $scope.message value in next digest cycle and watcher catches it as expected.
Take look on simple test:
$scope.$watch(function(){
console.log('trigger');
return $scope.message;
},
function(newMessage){
console.log('newMessage', newMessage)
});
function changeMessage(){
$scope.message='hi';
$timeout(function(){
$scope.message='hi12';
});
}
Output:
controller loading !
trigger
newMessage hi
trigger
trigger
newMessage hi12
trigger
There is no need to wrap changeMessage in setTimeout and $apply at the same time. If you need to skip some time before execution, just use:
function changeMessage(){
$timeout(function(){
$scope.message = 'message';
}/* or add time here, doesn't matter */);
}
Or just:
function changeMessage(){
$scope.message = 'message';
$scope.$apply();
}
Both methods calls $rootScope.$digest in the end. Here is more information: https://www.codingeek.com/angularjs/angular-js-apply-timeout-digest-evalasync/
$watch() only triggers between every $digest().
Detailed explaination about the $apply() and $digest()
In your case you keep updating the $scope.message in the current $digest() cycle.
You could change that by applying each new value to the $scope using $apply(). Like #Ajinkya wrote.
The only problem, with setting 2000ms as timeout, doesn't allways ensure it executes after the $digest(). On top of that, Angular has a build in timeout function. See below.
(function(){
angular.module('myApp', [])
.controller('myAppController', myAppController)
function myAppController($scope, $timeout){
console.log('controller loading !');
$scope.message = 'message1';
$scope.$watch('message', function(newMessage){
console.log('newMessage', newMessage)
});
function changeMessage(){
setTimeout(function () {
$scope.$apply(function () {
$scope.message='hi12';
});
}, 2000);
}
changeMessage();
}
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myAppController"></div>
Solution
The best way would be to call the build in $timeout function, without setting the time in milliseconds.
This way, angular allways ensures the $timeout will run after the latest $digest(). On top of that. You dont have to use the $scope.$apply(). Because the $timeout allready runs a $digest(), where $scope.$apply() is manually invoking a new $diggest() cycle.
(function(){
angular.module('myApp', [])
.controller('myAppController', myAppController)
function myAppController($scope, $timeout){
console.log('controller loading !');
$scope.message = 'message1';
$scope.$watch('message', function(newMessage){
console.log('newMessage', newMessage)
});
function changeMessage(){
$timeout(function () {
$scope.message='hi12';
});
}
changeMessage();
}
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myAppController"></div>

How to properly use the AngularJS digest cycle

I've been looking into the caveats of the AngularJS Digest cycle and I would like to better understand where the separation is between using it properly and improperly.
For example, if I have an AngularJS code that looks like this:
var myApp = angular.module("testApp", []);
myApp.controller("testController", ["$scope", "$timeout", function($scope, $timeout){
setTimeout(function(){
$scope.username = "Test User name";
}, 3000);
$timeout(function(){
$scope.username = "AngularJS User name";
}, 3000);
}]);
Why is setTimeout not being used as part of the Digest Cycle, whereas $timeout is, and how can I fix this to work?
Please keep in mind, I am looking not only for a code solution but for an explanation of why this occurs. As good as a code solution may come, it will not explain itself.
$timeout is an angularized version of setTimeout(), i.e. it is coded in such a way that it triggers a digest cycle. setTimeout() is a plain Javascript function that knows nothing about Angular or a digest cycle. Since setTimeout() is not a simple JS object, Angular cannot $watch it.
So the whole point of having functionality like $timeout is that they are angular-friendly versions of some Javascript functionality.
$timeout() and setTimeout() are not the same, $timeout is angularised.
It uses promise ($q service) internally, which would resolve after every digest cycle, automatically. Whereas setTimeout(), is just a trick with registering your callback function in the queue.
If you want setTimeout() to be part of digest loop, call it within the $scope.$apply():
setTimeout(function(){
$scope.$apply(function(){
$scope.username = "Test User name";
});
}, 3000));
A more generic explanation is that setTimeout does not work inside of angular because it puts its callback on the event loop, which angular does not watch. The same situation would happen if you were to create an XMLHttpRequest directly instead of $http.
Angular has made their own abstraction of these utilities/objects so that when they finish, the digest cycle will pick up any changes.

How to select text in an input after a change in Angular at the right time?

I have an input box. When the text changes, I need to select the text. I understand that there are many events going on and I need to wait for them to finish. I put in a timeout and it works. However I don't want to rely on a constant time. Is there any way how to select the text when Angular is finished changing the text?
Example HTML:
<input type="text" value="{{txt}}">
<button ng-click="select()">Press</button>
Example JS:
angular.module('MyApp', []).controller('MyCtrl', function ($scope, $interval) {
$scope.txt = "Hello World";
$scope.select = function () {
$scope.txt = "Bye World";
// doesn't work, too early
document.querySelector("input").setSelectionRange(0, 4);
// works
$interval(function () {
document.querySelector("input").setSelectionRange(0, 4);
}, 10, 1);
}
});
Working example is JSFiddle.
EDIT: From the answers it looks like using timeouts (even with 0 delay) is a common practice, but the question remains whether this will guarantee that the selection happens after Angular finishes updating the text.
You can use $timeout with 0 delay for this purpose.
$timeout(function(){
document.querySelector("input").setSelectionRange(0, 4);
});
Angular changes the DOM in next $digest cycle, it is extremely fast, but it won't be available as soon as you run $scope.x = ??. Normally we would use $timeout to "wait" in this case.
$timeout 0 delay is in fact good enough since angular's dirty checking (and $digest cycle) happens synchronously. (0 delay would only fire up when the current process is free).
If you really really want to guarantee it, here's how:
angular.module('MyApp', []).controller('MyCtrl', function ($scope, $timeout) {
$scope.txt = "Hello World";
$scope.select = function () {
$timeout(function () {
$scope.$apply(function () {
$scope.txt = "Bye World";
});
document.querySelector("input").setSelectionRange(0, 4);
});
};
});
You have to use timeout to wrap the $apply, because the scope function would trigger a $digest and you cannot call $digest within a $digest cycle($apply calls $digest for you). $apply here guarantee the scope variable is updated, hence your setSelectionRange would only happen after the update.
If you do not want to use $timeout, you can fire event with ng-blur. A blur event fires when an element has lost focus.
Working fiddle
HTML:
<div ng-controller="MyCtrl">
<form>
<input type="text" ng-blur="select()" value="{{txt}}">
<button>Press</button>
</form>
</div>
Script:
angular.module('MyApp', []).controller('MyCtrl', function ($scope, $interval) {
$scope.select = function () {
document.querySelector("input").setSelectionRange(0, 4);
}
});

Does AngularJS always know when something has changed, it's time to update?

I am reading AngularJS in Action by Lukas Ruebbelke to clear the concept of dirty checking as to how AngularJS works at a molecular level.
The author puts forward,
It is during the digest cycle that all watch expressions for a scope object
are evaluated. When a watch expression detects that a $scope property has
changed, then a listener function is fired.
Ocassionally a property is changed without AngularJS knowing about it. You
can manually kickstart a digest cycle vis $apply.
So, my question is what are those situations in a real web application when I need to kick off this digest cycle manually. And are those situations often seen? Kindly suggest.
This will come up any time an asynchronous callback returns from a non-angular library. e.g.
setTimeout(function() {
$scope.myVar = 1;
//Angular doesn't know when setTimeout finishes
//so you have to manually kick off a digest cycle.
$scope.$apply();
});
Angular has the $timeout service which takes care of starting a digest cycle for you but if you are using some third party library that takes a callback and doesn't have an angular wrapper then you will have to do this.
These situations can happen when using 3rd party libraries which provide some kind of data for example.
Say you use library-X which fires an event when something happened and new data is available, which you would like to render with AngularJS.
In these causes AngularJS does not know that data in the scope changed if you just directly set the variables.
That is why you should only modify scope variables inside the $apply function:
function MyController($scope) {
$scope.load = function() {
$scope.message = 'Loading...';
setTimeout(function() {
$scope.$apply(function () {
$scope.message = 'Finished loading!';
});
}, 2000);
}
}
It is also advised to use $scope.$apply(function () { /* update code */ }) instead of the single $scope.$apply() call, since it will properly catch errors and run the diggest regardless of any errors.

How to run function in AngularJS controller on document ready?

I have a function within my angular controller, I'd like this function to be run on document ready but I noticed that angular runs it as the dom is created.
function myController($scope)
{
$scope.init = function()
{
// I'd like to run this on document ready
}
$scope.init(); // doesn't work, loads my init before the page has completely loaded
}
Anyone know how I can go about this?
We can use the angular.element(document).ready() method to attach callbacks for when the document is ready. We can simply attach the callback in the controller like so:
angular.module('MyApp', [])
.controller('MyCtrl', [function() {
angular.element(document).ready(function () {
document.getElementById('msg').innerHTML = 'Hello';
});
}]);
http://jsfiddle.net/jgentes/stwyvq38/1/
See this post How to execute angular controller function on page load?
For fast lookup:
// register controller in html
<div data-ng-controller="myCtrl" data-ng-init="init()"></div>
// in controller
$scope.init = function () {
// check if there is query in url
// and fire search in case its value is not empty
};
This way, You don't have to wait till document is ready.
Angular has several timepoints to start executing functions. If you seek for something like jQuery's
$(document).ready();
You may find this analog in angular to be very useful:
$scope.$watch('$viewContentLoaded', function(){
//do something
});
This one is helpful when you want to manipulate the DOM elements. It will start executing only after all te elements are loaded.
UPD: What is said above works when you want to change css properties. However, sometimes it doesn't work when you want to measure the element properties, such as width, height, etc. In this case you may want to try this:
$scope.$watch('$viewContentLoaded',
function() {
$timeout(function() {
//do something
},0);
});
Angular initializes automatically upon DOMContentLoaded event or when
the angular.js script is evaluated if at that time document.readyState
is set to 'complete'. At this point Angular looks for the ng-app
directive which designates your application root.
https://docs.angularjs.org/guide/bootstrap
This means that the controller code will run after the DOM is ready.
Thus it's just $scope.init().
The answer
$scope.$watch('$viewContentLoaded',
function() {
$timeout(function() {
//do something
},0);
});
is the only one that works in most scenarios I tested. In a sample page with 4 components all of which build HTML from a template, the order of events was
$document ready
$onInit
$postLink
(and these 3 were repeated 3 more times in the same order for the other 3 components)
$viewContentLoaded (repeated 3 more times)
$timeout execution (repeated 3 more times)
So a $document.ready() is useless in most cases since the DOM being constructed in angular may be nowhere near ready.
But more interesting, even after $viewContentLoaded fired, the element of interest still could not be found.
Only after the $timeout executed was it found. Note that even though the $timeout was a value of 0, nearly 200 milliseconds elapsed before it executed, indicating that this thread was held off for quite a while, presumably while the DOM had angular templates added on a main thread. The total time from the first $document.ready() to the last $timeout execution was nearly 500 milliseconds.
In one extraordinary case where the value of a component was set and then the text() value was changed later in the $timeout, the $timeout value had to be increased until it worked (even though the element could be found during the $timeout). Something async within the 3rd party component caused a value to take precedence over the text until sufficient time passed. Another possibility is $scope.$evalAsync, but was not tried.
I am still looking for that one event that tells me the DOM has completely settled down and can be manipulated so that all cases work. So far an arbitrary timeout value is necessary, meaning at best this is a kludge that may not work on a slow browser. I have not tried JQuery options like liveQuery and publish/subscribe which may work, but certainly aren't pure angular.
I had a similar situation where I needed to execute a controller function after the view was loaded and also after a particular 3rd-party component within the view was loaded, initialized, and had placed a reference to itself on $scope. What ended up working for me was to setup a watch on this scope property and firing my function only after it was initialized.
// $scope.myGrid property will be created by the grid itself
// The grid will have a loadedRows property once initialized
$scope.$watch('myGrid', function(newValue, oldValue) {
if (newValue && newValue.loadedRows && !oldValue) {
initializeAllTheGridThings();
}
});
The watcher is called a couple of times with undefined values. Then when the grid is created and has the expected property, the initialization function may be safely called. The first time the watcher is called with a non-undefined newValue, oldValue will still be undefined.
Here's my attempt inside of an outer controller using coffeescript. It works rather well. Please note that settings.screen.xs|sm|md|lg are static values defined in a non-uglified file I include with the app. The values are per the Bootstrap 3 official breakpoints for the eponymous media query sizes:
xs = settings.screen.xs // 480
sm = settings.screen.sm // 768
md = settings.screen.md // 992
lg = settings.screen.lg // 1200
doMediaQuery = () ->
w = angular.element($window).width()
$scope.xs = w < sm
$scope.sm = w >= sm and w < md
$scope.md = w >= md and w < lg
$scope.lg = w >= lg
$scope.media = if $scope.xs
"xs"
else if $scope.sm
"sm"
else if $scope.md
"md"
else
"lg"
$document.ready () -> doMediaQuery()
angular.element($window).bind 'resize', () -> doMediaQuery()
If you're getting something like getElementById call returns null, it's probably because the function is running, but the ID hasn't had time to load in the DOM.
Try using Will's answer (towards the top) with a delay. Example:
angular.module('MyApp', [])
.controller('MyCtrl', [function() {
$scope.sleep = (time) => {
return new Promise((resolve) => setTimeout(resolve, time));
};
angular.element(document).ready(function () {
$scope.sleep(500).then(() => {
//code to run here after the delay
});
});
}]);
Why not try with what angular docs mention https://docs.angularjs.org/api/ng/function/angular.element.
angular.element(callback)
I've used this inside my $onInit(){...} function.
var self = this;
angular.element(function () {
var target = document.getElementsByClassName('unitSortingModule');
target[0].addEventListener("touchstart", self.touchHandler, false);
...
});
This worked for me.
$scope.$on('$ViewData', function(event) {
//Your code.
});

Categories

Resources