$timeout use inside AngularJS factory - javascript

I used AngularJS factory to create new instance model for my project, and each model contain a progress value that will be incremented, paused, and set back to 0 based on "start", "pause", and "stop" user actions.
app.factory('ModelA', ['$timeout', function($timeout) {
function ModelA(progress) {
this.progress = progress;
};
ModelA.prototype = {
startProgress: function() {
this.counterFunction();
},
counterFunction: function() {
this.progress++;
if(this.progress == 100) {
this.progress = 0;
}
//console.log(this.progress);
//console.log(this.counterFunction);
progressTimeout = $timeout(this.counterFunction, 1000);
},
// Haven't tested the method below
pauseProgress: function() {
$timeout.cancel(progressTimeout);
},
stopProgress: function() {
$timeout.cancel(progressTimeout);
this.progress = 0;
}
};
return ModelA;
}]);
For some reason, when I call startProgress() in the ng-click expression function, the progress will increment 1 and then stop. I added logs to check this.counterFunction for every call. I realized it only prints out 1 and the whole counterFunction on the first time. As for the second time, this.progress will be NaN and the counterFunction will show undefined.
I'm new to AngularJS, could someone please help me out? Thanks.

The this object in the function called by the $timeout, i.e. this.counterFunciton is not the ModelA instance, therefore you should use
$timeout(this.counterFunction.bind(this), 1000) instead.
You can read up this article about binding this object in JavaScript.
A working codepen for your reference.

The execution context this changes when the $timeout gets executed. You would need to preserve the ModelA this in $timeout(this.counterFunction.bind(this), 1000). You bind and pass the this to this.counterFunction and thus counterFunction has the right access to this.progress.
Check here more info about the this problem here. $timeout is the wrapper for window.setTimeout
https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout#The_this_problem

Related

How to update window after each iteration of a loop in js?

I'm trying to update the window after each iteration of the loop. The code in question is below. After picking the initial live cells I press next which should iterate through the loop. It reaches the correct final state, but does not display the states in-between.
Plnkr link: https://plnkr.co/edit/Gtp87m48YT5wGbSytQX4?p=preview
I understand that I probably have to use setInterval, but whenever I try implementing it, it just freezes the window or skips to the end. I've tried using the angular $interval, but I have the same problem
$scope.next = function newgrid() {
var num=3;
while(num>0)
{
$scope.history.push($scope.board);
$scope.board = computeNext($scope.board);
num--;
}
};
Edit updated code. Now it just stays stuck on the initial state:
$scope.next = function newgrid() {
$interval(function() {
$scope.history.push($scope.board);
$scope.board = computeNext($scope.board);
}, 1000);
};

How do I create and focus a new element in AngularJS?

I have a form with a dynamic number of inputs, controlled by AngularJS.
<body ng-app="mainApp" ng-controller="CreatePollController" ng-init="init(3)">
<form id="createPollForm">
<input class="create-input" ng-repeat="n in questions" id="q_{{$index}}" name="q_{{$index}}" type="text" ng-keypress="createInputKeypress($event);"/>
Add Question
</form>
</body>
This is being controlled by the following angular code:
app.controller('CreatePollController', function($scope) {
$scope.questions = [];
$scope.init = function(numOfInputs){
for(var i = 0; i < numOfInputs; i++){
$scope.questions.push({
"questionText":""
});
}
};
$scope.addQuestion = function(){
$scope.questions.push({
"questionText":""
});
};
$scope.createInputKeypress = function(e){
if(e.keyCode === 13){
e.preventDefault();
var idx = Number(e.target.id.replace("q_", ""));
if(idx === this.questions.length - 1){
this.addQuestion();
}
// Wait for angular update ????
var nextId = "#q_" + (++idx);
$(nextId).focus();
}
};
});
Currently, when the user hits the Enter key while focused on a text input, the createInputKeypress function is called and the browser focuses the next input in the form. However, if you are currently focused on the last element in the form, it adds a new question to the questions array, which will cause another input to be generated in the DOM.
However, when this new element is created, the focus() call isn't working. I suspect this is because angular doesn't add the new element right away, so trying to use jQuery to locate and focus the new element isn't working.
Is there a way to wait for the DOM to be updated, and THEN focus the new element?
As you might already know, javascript is turn based, that means that browsers will execute JS code in turns (cycles). Currently the way to prepare a callback in the next javascript cycle is by setting a callback with the code we want to run on that next cycle in a timeout, we can do that by calling setTimeout with an interval of 0 miliseconds, that will force the given callback to be called in the next javascript turn, after the browser finishes (gets free from) the current one.
Trying to keep it simple, one browser cycle executes these actions in the given order:
Scripting (where JS turn happen)
Rendering (HTML and DOM renderization)
Painting (Painting the rendered DOM in the window)
Other (internal browser's stuff)
Take a look at this example:
console.log(1);
console.log(2);
setTimeout(function () {
console.log(3);
console.log(4);
}, 0);
console.log(5);
console.log(6);
/** prints in the console
* 1 - in the current JS turn
* 2 - in the current JS turn
* 5 - in the current JS turn
* 6 - in the current JS turn
* 3 - in the next JS turn
* 4 - in the next JS turn
**/
3 and 4 are printed after 5 and 6, even knowing that there is no interval
(0) in the setTimeout, because setTimeout basically prepares the given callback to be called only after the current javascript turn finishes. If in the next turn, the difference between the current time and the time the callback was binded with the setTimeout instruction is lower than the time interval, passed in the setTimeout, the callback will not be called and it will wait for the next turn, the process repeats until the time interval is lower than that difference, only then the callback is called!
Since AngularJS is a framework wrapping all our code, angular updates generally occur after our code execution, in the end of each javascript turn, that means that angular changes to the HTML will only occur after the current javascript turn finishes.
AngularJS also has a timeout service built in, it's called $timeout, the difference between the native setTimeout and angular's $timeout service is that the last is a service function, that happens to call the native setTimeout with an angular's internal callback, this callback in its turn, is responsible to execute the callback we passed in $timeout and then ensure that any changes we made in the $scope will be reflected elsewhere! However, since in our case we don't actually want to update the $scope, we don't need to use this service, a simple setTimeout happens to be more efficient!
Knowing all this information, we can use a setTimeout to solve our problem. like this:
$scope.createInputKeypress = function(e){
if(e.keyCode === 13){
e.preventDefault();
var idx = Number(e.target.id.replace("q_", ""));
if(idx === this.questions.length - 1){
this.addQuestion();
}
// Wait for the next javascript turn
setTimeout(function () {
var nextId = "#q_" + (++idx);
$(nextId).focus();
}, 0);
}
};
To make it more semantic, we can wrap the setTimeout logic
in a function with a more contextualized name, like runAfterRender:
function runAfterRender (callback) {
setTimeout(function () {
if (angular.isFunction(callback)) {
callback();
}
}, 0);
}
Now we can use this function to prepare code execution in the next javascript turn:
app.controller('CreatePollController', function($scope) {
// functions
function runAfterRender (callback) {
setTimeout(function () {
if (angular.isFunction(callback)) {
callback();
}
}, 0);
}
// $scope
$scope.questions = [];
$scope.init = function(numOfInputs){
for(var i = 0; i < numOfInputs; i++){
$scope.questions.push({
"questionText":""
});
}
};
$scope.addQuestion = function(){
$scope.questions.push({
"questionText":""
});
};
$scope.createInputKeypress = function(e){
if(e.keyCode === 13){
e.preventDefault();
var idx = Number(e.target.id.replace("q_", ""));
if(idx === this.questions.length - 1){
this.addQuestion();
}
runAfterRender(function () {
var nextId = "#q_" + (++idx);
$(nextId).focus();
});
}
};
});

How to bind angular scope to the result of a web worker using an interval?

I need to update a variable from my scope based on the "callback" of web workers. These web workers are basically an interval that return value at a specific interval.
Here is my worker.js
var timer = false;
var onmessage = function (e) {
var value = e.data[0];
var stock = e.data[1];
var interval = e.data[2];
if (timer) {
clearInterval(timer);
}
timer = setInterval(function () {
stock += value;
postMessage(stock);
}, interval)
}
I know how to get the result from web workers using a promise but, as I expected, it only works for the first callback.
app.factory('workerServices', ['$q', function ($q) {
var worker = new Worker('worker.js'),
defer = $q.defer();
worker.onmessage = function (e) {
console.log('Result worker:' + e.data); //displayed each "interval"
defer.resolve(e.data);
}
return {
increment: function (value, stock, interval) {
defer = $q.defer();
worker.postMessage([value, stock, interval]);
return defer.promise;
}
};
}]);
app.controller('mainCtrl', ['$scope', 'workerServices', function ($scope, workerServices) {
var value = 1,
stock = 0,
interval = 300;
workServices.increment(val, stock, interval).then(function (newStock) {
$scope.stock = newStock; //updated once
});
}]);
So I tried to bind the result to the scope directly to see if it works (which I didn't like since I wanted to use factories for my web workers).
And it didn't work. I don't know why but my scope stay the same even if the console.log works properly.
app.controller('mainCtrl', function ($scope) {
var value = 1,
stock = 0,
interval = 300;
var worker = new Worker('worker.js');
worker.postMessage([value, stock, interval]);
worker.onmessage = function (e) {
console.log(e.data) //properly displayed
$scope.result = e.data; // never updated
};
});
What am I missing here? How can I can "continuously" update a variable from my scope to the results of a web worker?
Webworker event onmessage is happening outside angular context, so angular digest system doesn't have any idea about to update bindings. In such case we need to explicitly say angular to kick off digest cycle manually by calling $scope.$apply().
Rather I'd say rather use $timeout which would be the safest way to apply digest cycle. because sometime directly running $apply method can cause an digest cycle is in already progress error by conflicting with currently running cycle.
worker.onmessage = function (e) {
console.log(e.data) //properly displayed
$timeout(function(){
$scope.result = e.data; // never updated
});
};
In the second case, could you try $scope().$apply() after $scope.result = data ?
And in your first case, AFAIK you cant resolve the same promise twice and thats the reason why you are not getting the result you expect. I would suggest you to go for a subscriber-listener pattern to achieve this $broadcast/$emit methods. This tutorial will give you a good start.

angularjs : update value of a variable After external timeout function call

I have a controller and a factory. A function(myfunc) inside the factory(searchFactory) is called by ng-click of a button. after which I call a function(waitfunction) which is outside the conntroller. In that function timeout of 2 sec is used and then a value is changed and returned to the controller. How can I make sure that the value is updated in the controller after 2 sec. JSfiddle : http://jsfiddle.net/zohairzohair4/cRr9K/1334/
var search_name
var angularjsapp = angular.module('graphApp', ['ngAnimate', 'ui.bootstrap']);
angularjsapp.factory('searchFactory', function() {
//return $resource('friends.json');
return{
myfunc:function(search_name){
console.log('ok')
keyword_type = 1
loading_value = waitfunction(search_name)
console.log(loading_value)
return loading_value
}
}
});
angularjsapp.controller('AccordionDemoCtrl', function($scope,searchFactory) {
$scope.count = 0;
$scope.namesPerPage = 10
$scope.currentPage = 1;
$scope.searchFactory = searchFactory.myfunc
});
function waitfunction(search_name){
value = 0
window.setTimeout(function () {
value = 1;
}, 2000);
return value
};
Using the setTimeout function will bypass angular's dirty checking. If you want to use async functionality outside of angulars dirty-checking, you need to trigger angular to do the dirty checking again. You can do that by calling $scope.$apply(); or wrap your async call with a function like this:
$scope.$apply(function() {
...
});
For your specific need - angular already have a number of async methods to replace the default javascript ones and i'd suggest you use that instead of javascripts timeout:
$timeout(function () {
value = 1;
}, 2000);
You can read more about $timeout here: https://docs.angularjs.org/api/ng/service/$timeout
Your waitfunction doesn't seem to be doing what you want it to do. It will return the value long before the timeout changes it. This happens because you're just referencing a simple value. If you pass an object and modify that objects property, then you can achieve what you're trying to do:
function waitfunction(search_name){
var obj = { value: 0 };
$timeout(function () {
obj.value = 1;
}, 2000);
return obj;
};
You then need to bind to the returning objects .value property instead.
I see your plunker and there is a lot of work to be done. It doesn't seem like you're binding the resulting object to anything. I think this post helps to solve atleast the async problem associated with calling setTimeout and the problem of binding to simple values.
You need to use $timeout
$timeout(function() {
value=1;
}, 2000);

Understanding window resize event with scope.$apply with $watch on a function

Recently I found this http://jsfiddle.net/jaredwilli/SfJ8c/ and I am able to get it works.
var app = angular.module('miniapp', []);
app.directive('resize', function ($window) {
return function (scope, element) {
var w = angular.element($window);
scope.getWindowDimensions = function () {
return {
'h': w.height(),
'w': w.width()
};
};
scope.$watch(scope.getWindowDimensions, function (newValue, oldValue) {
scope.windowHeight = newValue.h;
scope.windowWidth = newValue.w;
}, true);
w.bind('resize', function () {
scope.$apply();
});
}
})
But the problem is, I don't know how it works? Why scope.$apply()? What is the purpose of it? Why scope.getWindowDimensions will get updated when window is resized?
The first argument to $watch can be a string or a function. If you pass a string like $scope.$watch('foo', it is watching $scope.foo. If you pass a function, then the watch is on the return value of the function. Angular will fire the function on every $digest cycle. If the returned value is different than the previous $digest cycle, the callback function (second parameter) will fire. In this code, scope.getWindowDimensions is a function being passed and when its return value is different, the callback will fire. So, every $digest cycle, if w.height() or w.width() have changed, the callback is fired, and the $scope properties are updated. Finally, you have the true (third) parameter set, which makes the $watch a deep watch, so that Angular will thoroughly check the object, to tell whether it is identical, even though it's a new object every time. Without this, Angular will do a quick check, see that it's a new object, and start an infinite loop.
scope.$watch(scope.getWindowDimensions, function (newValue, oldValue) {
scope.windowHeight = newValue.h;
scope.windowWidth = newValue.w;
}, true);
Lastly, the code above won't do anything unless a $digest cycle is triggered. The following code attaches an event listener to window so that the function is fired when the window is resized. scope.$apply() just triggers a $digest cycle so that scope.getWindowDimensions will be checked, and the callback will be fired.
w.bind('resize', function () {
scope.$apply();
});
With all that said, I find this code to be a bit awkward. This is how I'd write it. This way makes a lot more sense to me - easier to read, and should be more performant.
app.directive('resize', function ($window) {
return function (scope, element) {
var w = angular.element($window);
w.bind('resize', function () {
// trigger $digest when window is resized and call `update` function
scope.$apply(update);
});
update(); // initial setup
function update() {
var height = w.height();
var width = w.width();
scope.windowHeight = height;
scope.windowWidth = width;
scope.style = function() {
return {
'height': (height - 100) + 'px',
'width': (width - 100) + 'px'
};
};
}
}
})
Live demo here.
First of all its better to google first and then ask your doubt here but anyways if you didn't get anything from it here is the summary:-
w.bind('resize',callback)
function binds the resize event of window to w therefore if window resize value of w.height() and w.width() change So you are having scope.$watch(scope.getWindowDimensions,callback); watch the changes in scope.getWindowDimensions which is definately gonna change on changing the window size which in turn calll the callback function.
Now why we are using scope.$apply();The reson is simple w.bind is out of scope of angular so you need to run the digest cycle which is going to run manually by scop.$apply.
Sorry for bad english :-P

Categories