I've been wracking my brain over this for a few days, so I figured it's time to finally ask somebody who actually knows what they're doing. ;)
I'm building an AngularJS app and getting an odd behavior when I'm iterating through a couple of my for loops. I'm trying to find the 'best' option to add to a list given a few possibilities, and so here's my general process for how I'd like to get it done:
The Idea:
1) add each possibility to the desination list, say $scope.list1
2) calculate the 'score' based on the state of the lists
3) remove the possibility from $scope.list1
4) repeat for all possibilities.
Here's the basic idea of what I'm running in my controller:
$scope.getSuggestions = function(){
//for each possibility to consider:
for(var i = 0; i < possibilities.length; i++){
//add the possibility to the scope as per usual
$scope.list1.push(possibilities[i]);
//calculate the score given the state of the scope
$scope.calculateScore();
//save the score in a new object on the scope
$scope.results.push({"option": possibilities[i],
"score": $scope.score
});
//remove the possibility to reset the scope back to its original state
$scope.list1.pop();
}
};
The Problem:
It seems as if my for loop is actually moving on to further iterations before the code has finished executing. My $scope.calculateScore() function may take a bit of time to run, and it seems like it's being ignored by the for loop. I'm not doing anything obviously asynchronous in that function; it's mostly other for loops based on the state of the $scope and some math to get a 'score'.
My question:
Why does Angular think it's okay to continue blazing through a for loop when the code inside hasn't finished executing? I see how it could make sense in certain cases, but in this one it's vital that everything happens in order, after the previous functions have finished. Do you all have any suggestions on how to make this work?
Note: You can see the code in its entirety by viewing the source at http://www.PlatTheLeague.com
The actual code for calculateScore() is in scripts/controllers/teamBuilderCtrl.js, where the function is called $scope.populateGamePredictions(). My initial tries at the suggestions are in the same file, called $scope.getChampSuggestions. To see the problem, drag an item from the list on the left to the box that says 'Their Top' and then click on the 'Champion Suggestions' tab. That tab should be populated with the results of $scope.results listed above in the pseudo-code, but it seems that the teamScore on my scope object isn't being set properly.
Cheers!
Maybe you have some logics that change the internal states of $scope from outside of AngularJS, maybe from some kinds of events from DOM, ajax callbacks, setTimeout ... In that case, $apply method can help. You can have more references of $apply from here: https://github.com/angular/angular.js/wiki/When-to-use-$scope.$apply()
As it turns out, The above idea works in the typical synchronous manner, as I've proved to myself in this plunker: http://plnkr.co/edit/9spVbf5JWx9Hnq53sHSV?p=preview
My issue arose from the fact that the data model being worked on by my calculateScore() function was not formatted exactly how I thought it would be (my own fault, thanks to the semi-complicated nature of the app...) which meant that the scores weren't being updated properly in the first place, though the code was running as expected.
Related
I have started a demo app and come across this problem where my parts that I am adding to the repairs are not being properly proccessed. This is my first angular that I am building.
To get the bug press update on one of the repair cards and then scroll down to the quantity and repair item section at the bottom of the form and try to add a new item. it doesnt regester what is in the form.
This is the codepen with the full view that doesnt work.
This is the function in the controller to pass the info the factory.
$scope.addPartsPerRepair = function() {
partFactory.addPartsPerRepair($scope.newPartsPerRepair);
$scope.newPartsPerRepair = {};
};
This is the function in the factory to add the new repair to the array partsPerRepair
factory.addPartsPerRepair = function(newPartsPerRepair) {
partsPerRepairs.push(newPartsPerRepair);
};
After I abstracted away the repair factory and simplified my view we are left with this
This is the codepen with the partial view that does work.
I have been over this code for the past 6 hours trying to fix this one probelm and I can not see my issue.
Secondly, This is more of an add-on question, Does anyone have any advice on how to save the partsPerRepair to the actual repair[Idx] instead of in one array called partsPerRepair that every repair accesses. I thought I might be able to add an array called parts to each repair and store quanity and item name in that array but that has proven to be more difficult then I can manage. I realize I am asking two different questions but since your here reading I figured you might be willing.
Thanks in advance for any help.
An updated CodePen.
So I've tried to resolve both your questions here, but all I have edited is the arra yof repairs and the add parts method, which means there is alot of cleaning up chanegs to be made like deleting the old partsPerRepairs and its references.
First thing newPartsPerRepair needed to be intialized this is why add parts was not working the function was passsing undefined(anything used as model in ng-model needs to be intialized), which is done in the controller - $scope.newPartsPerRepair = {};.
Second I moved the partsPerRepairs array into the repairs array and called it parts this is under each customer in the array, this is to answer your second question.
Third I changed the way the ng-repeats work to use these new parts arrays - ng-repeat="partsPerRepair in repairs[$index].parts track by $index"
Finally I moved and edited the addNewParts to the repairs factory so it used the new parts array and could access the repairs array.
Hope this all helps.
I'm encountering performance issues, I think due to lots of watchers in the page (more than 4000!!). The scenario is a (small, about 5) list of items in ng-repeat once, each one contains another ng-repeat for every day of week (so 7), and in each day container there are 1 or 2 input field. Each day's element has its own scope and controller and some watch at parent's properties, in order to update parent state at child changes. So a bit complex scenario...imagine an agenda view where each day as some input fields or buttons which update same property in the main scope, like "10 days selected/filled/clicked".
I started with about 5000 watchers, now reduced to about 4000 removing some filters and switching to translate-once directive insted of translate (angular-translate).
So the main question is:
How to further reduce the number of watchers?
Is every child scope inheriting the parent watchers, resulting in 7x for each watcher? If I remove child's controllers, leaving the job to the the parent (passing in the function the child item), will I decrease the number of watchers? Could this be a solution? Any help is appreciate.
In our experience that number of watchers cause no speed problems. The performance problems we have encountered in the last 8 months of development on a single big application were caused by slow third part's components.
For example, we have a page with two drag and drop trees with 14.600 watchers (because of high number of items in both trees). We experienced performance problems because of the component used, angular-ui-tree, and we reduced them opening the page with most of the tree collapsed.
We cannot change that component because it is the only one which features drag and drop between trees, but in another page where we had drag & drop between simple lists we have tried those two components: angular-dragdrop and angular-drag-and-drop-lists. The first had a lot of performance problems (with about 500 items) while the second run really really fast. In his documentation on github, section "Why another drag & drop library?" you can read why it is so fast and why the other is so slow.
So, I can speculate that third part's components bring you the real performance problems, and not the watchers.
In any case, we often write our watchers with a check like the one below to not run the code unless needed.
$scope.$watch('variableToWatch', function(newValue, oldValue) {
if (newValue === oldValue) {
return;
}
... watcher code ...
}
Another way to reduce watchers from html is using one-time-binding.
Example:
<div ng-if="::vm.user.loggedIn"></div>
Related to performance... - One pattern i came up with is to use a private object and assign the prototype of a function for easy access. then in any function ,controllers, directives...ect you can access the prototype of other function,controllers,directives easily. instead of using watchers you can use this pattern like a event loop. instead of angular running 300+ watchers every digest cycles. using this pattern only what triggers the function call matters.
An example of this pattern
var private = {} //accesable to entire app
var app = angular.module('some-app',[])
.controller('someCtrl',['$scope',someCtrl])
.directive('someDirective',someDirective);
function someCtrl($scope){
private.someCtrl = someCtrl.prototype
someCtrl.prototype.update = function(someDate){
//do something....
//access to someCtrl arguments
$scope.something = someDate
}
}
function someDirective(){
var someCtrlProto = private.someCtrl;
return{
link:function(scope ,elm ,attr){
elm[0].addEventListener('click',fucntion(){
someCtrlProto.update(someData)
});
//or
elm[0].addEventListener('click',someCtrlProto.update) //to trigger someCtrl.update from here
}
}
}
I would like to know what is the best way to keep checking a variable in AngularJS.
I made a simple clicker game where the user keeps gathering gold, so I need to be constantly checking the number of gold so I can perform some functions accordingly. The only thing I know is $interval, but I don't know if that's the best way since I have to write a number of seconds.
Thank you in advance.
Wherever your variable is stored (controller/directive):
$scope.goldAmount = 0; // or whatever initial value you so desire
Set a watch function
$scope.$watch('goldAmount', function(newAmount, oldAmount){
// Do something with the amount when it changes
});
$watch allows you to 'watch' any variable within your $scope and run a call back when it changes.
Docs can be found here:
https://docs.angularjs.org/api/ng/type/$rootScope.Scope
With some help from StackOverflow community I was able to get my dirty flag implementation to work, based on this example: http://www.knockmeout.net/2011/05/creating-smart-dirty-flag-in-knockoutjs.html
It does exactly what I want, except for a single use case that I don't know how to solve.
Basically I have a select menu that gets automatically populated from the database. This select menu also has an option to make an Ajax call to my back end and have the list of options refreshed, database updated and return the result. This is where things get hairy for me.
First method works fine, however, it has to re-index and re-apply my entire viewModel and takes about 2-3 seconds, running on a local machine with 16gigs of ram and SSD.
jsondata.component.available_tags = result.available_tags;
ko.mapping.fromJS(jsondata, viewModel);
Second method also works, and pretty much instantaneous, however, it sets of isDirty() flag, which I would like to avoid, because this data is already coming from the database and I wont need to save it. I can not use isDirty.reset() method either, because if isDirty was set by something else before I clicked an menu option to update available_tags, it will reset that too. Which I would also like to avoid.
viewModel().component.available_tags(result.available_tags);
My question is: With the first method, can I force UI refresh with ko.mapping.fromJS() on a particular element and not entire dataset? Or, with a second method, can I avoid setting isDirty flag set when available_tags are updated? The twist is that I still need to keep available_tags as an observable, so the select menu is automatically generate/updated.
UPDATE: I was able to update mapping for that one single element with
ko.mapping.fromJS(result.available_tags, {}, viewModel().component.available_tags);
but that immediately set off isDirty flag... Argh
In addition to Tomalak's suggestions, which I totally agree with, maybe the toJSON method can help you out in similar cases where you don't want to split the model. If your dirty flag implementation uses ko.toJSON as a hash function, as Ryan Niemeyer's does, you can give your model (on which the dirty flag is active) a toJSON method, where you do something like this:
function MyObjectConstructor() {
this.someProperty = ko.observable();
this.somePropertyNotUsedInDirtyFlag = ko.observable();
}
MyObjectConstructor.prototype.toJSON = function () {
var result = ko.toJS(this);
delete result.somePropertyNotUsedInDirtyFlag;
return result;
};
Please be aware that this is also used to serialize the object in some other occassions, such as ajax calls. It's generally a handy function for removing computeds and such from your objects before using them in a different context.
Here's the fiddle: http://jsfiddle.net/K5dsh/
I'm trying to make a simple calculator that evaluates the difference between the numbers its given, and gives an answer based on that. It has two problems:
1.) The answer my script gives is always the result of my 2nd else if statement, even if the input matches the conditions before it.
2.) The answer does not change even when there's a new input that should give a different result.
Does anyone see what the problems are? Thank you.
As Ivan pointed out, you're never updating the values of high, low and common with the values entered into the textboxes. You should be assigning the values inside the calculate function. See updated fiddle.
Also, you may want to invest some time into learning a framework like Knockout.js. It makes data-binding HTML form elements to JavaScript view models incredibly simple.
Your variables are defined only once. You should change it so that every time your button is clicked, it grabs the new variables. Here is an example: http://jsfiddle.net/Vd8n4/
Your values aren't being updated.
Here's how I would go about it. I'd change your function so that it starts off like this. That way, every time it is called, it updates the high, low, and common values.
function calculate() {
var high = document.getElementById('highRi').value;
var low = document.getElementById('lowRi').value;
var common = document.getElementById('comm').value;