angular: strange behavior with ng-options - angularjs

I stumbled across a strange problem with ng-options. Consider a simple select:
<select ng-model="x" ng-options="n for n in [1,2,3]"></select>
where the model value gets assigned in the controller:
$scope.x = 3;
This works fine; the select element shows 3. However, if the assignment is delayed,
$timeout(function() { $scope.x = 3 }, 50);
The select shows up blank. But only if the model value is the last option in the list! In other words, this works fine:
$timeout(function() { $scope.x = 2 }, 50);
Then the select element shows 2. Same deal with 1. But 3 shows up blank.
What the?
Here's a plunk that demonstrates.
It's a problem that shows up with angular 1.2.26. Earlier versions are okay (you can see this for yourself by changing the angular version used by the plunk).
But my project has all kinds of dependencies and bower keeps pushing my angular to 1.2.29. Anybody got any idea how I might work around this issue?

I had the same problem and eventually solved it by telling css not to display these options:
option[value^=\?]{
display: none;
}
This has the effect of telling CSS not to display any option with a value attribute value starting with "?" (Note that the question mark must be escaped.)

This appears to be a bug in version 1.2.26 / 1.2.28, but works for 1.3.*.
I've submitted this as an issue to Angular:
https://github.com/angular/angular.js/issues/10718
To work around this issue, initialize the scope variables in your ngController (outside of $timeout):
function ctrl($scope, $timeout) {
$scope.p_immediate = 3;
$scope.s_immediate = "third";
$scope.p_delayed = 0;
$scope.s_delayed = "first";
$timeout(function() {
$scope.p_delayed = 3;
$scope.p_not_last = 2;
$scope.s_delayed = "third";
$scope.s_not_last = "second";
}, 50);
}
Demo

Related

UIGRID: Changing cellFilter in columnDefs is not well updated

I need to dynamically change the decimal precision of a column.
I have tried different approaches and non of them worked.
I did a quick example in Plunker in which there is a button to swap data. They have different data and different columnDefs: one has 3 decimals and the other 4.
You can see there that the first rows maintain the first format.
I'm using notifyDataChange in gridApi but is not working for this kind of change:
$scope.gridApi.core.notifyDataChange( this.uiGridConstants.dataChange.ALL );
This is a problem for me because in my case I want to keep the same data but dynamically change the decimal precision of a column.
Does anybody knows how to refresh/reload that new columnDefs configuration?
I would really appretiate it!
Sadly it seems none of the usual methods to refresh/update the grid will work with this use case.
I managed to make it work though by deleting all rows and then recreating them after a $timeout.
I updated your plunker here.
The main edit is as follows:
$scope.swapData = function() {
$scope.gridOpts.data = []{
$timeout(function() {
if (!$scope.isData2) {
$scope.gridOpts.data = data2;
$scope.gridOpts.columnDefs = columnDefs2;
}
else {
$scope.gridOpts.data = data1;
$scope.gridOpts.columnDefs = columnDefs1;
}
$scope.isData2 = !$scope.isData2;
});
};
Looks like there is an answer on the ui-grid github repo.
From a contributor:
The cellFilter is joined in with the cell template when the cell
template is fetched/used. It doesn't get updated live.
With possible solution:
colDef.cellFilter = 'date:col.filterFormat'
uiGridApi.core.registerColumnsProcessor(function(columns, rows){
angular.forEach(columns, function(col){
var filterFormat = col.colDef.filterFormat;
if ( filterFormat ){ col.filterFormat = filterFormat; }
});
return rows;
}, 40);

AngularJS : Why the data is not displayed in view may I use $scope.apply?

I am getting data from service and display on view using ng-repeat .Actually I am getting event when user scroll to bottom mean when user reached to bottom I will do something.When It reached to bottom I am changing the contend of my array .I am getting the correct contend in ng-repeat array (display array) but it is not reflect on view why ? May I use $scope.apply() or $scope.digest()
Here is my code
http://plnkr.co/edit/XgOxJnPXZk4DneJonlKV?p=preview
Here I am changing the contend of my display array which is not reflect on view
if (container[0].offsetHeight + container[0].scrollTop >= container[0].scrollHeight) {
if(scope.var<scope.arrays.length)
scope.display=[];
var nextvar =++counter;
var increment=counter+1
console.log("nextvar:"+nextvar+"increment:"+increment)
scope.display=scope.arrays[nextvar].concat(scope.arrays[increment]);
console.log(scope.display)
}
As #Claies mentioned you should use apply(). Though the digest() would probably have worked as well.apply() calls digest() internally. He also mentioned that your variable that seems to be storing the page number gets reset to 0 each time you scroll. You should store that in a scope variable outside that handler.
I tried to fix with minimum change
http://plnkr.co/edit/izV3Dd7raviCt4j7C8wu?p=preview
.directive("scrollable", function() {
return function(scope, element, attrs) {
var container = angular.element(element);
container.bind("scroll", function(evt) {
console.log('scroll called'+container[0].scrollTop);
var counter = scope.page;
if (container[0].scrollTop <= 0) {
if (scope.var > 0)
scope.display = scope.arrays[--scope.var].concat(scope.arrays[scope.var+1]);
}
if (container[0].offsetHeight + container[0].scrollTop >= container[0].scrollHeight) {
if (scope.var < scope.arrays.length)
scope.display = [];
var nextvar = ++counter;
var increment = counter + 1
console.log("nextvar:" + nextvar + "increment:" + increment)
scope.display = scope.arrays[nextvar].concat(scope.arrays[increment]);
console.log(scope.display)
scope.page = counter;
}
scope.$apply();
});
};
})
generally I would have implemented this differently. For example by having a spinning wheel on the bottom of the list that when displayed you get the rest of data.
It is difficult to give you a full working plunker. Probably you should have multiple JSON files in the plunker, each containing the data for one page so that we can add the data to the bottom of the display list.
After you modify display array you just have to call scope.$apply() so that it runs the $digest cycle and updates the view. Also you need the initialize scope.var either in your controller or the directive and modify it conditionally.
I dont if this is what you want. I have modified the plunker take a look.
http://plnkr.co/edit/J89VDMQGIXvFnK86JUxx?p=preview

Can't sum values in Angularjs if one value is an empty string

I am building a simple Appgyver mobile app using Angularjs and Coffeescript - I'm a beginner with both of these.
I wish to determine the total cost for a list of up to 20 items stored on the database. However, there may be less than 20 items.
I have attempted to do the calculation with ng-bind, which works perfectly as long as all strings contain values. However, if there are less than 20 pairs (values go up to q20 and p20) then the calculation returns NaN.
I would like to determine the total of all existing values for the list. I have looked at numerous examples on stackoverflow, Angularjs.org and other sites and have experimented with a myriad of alternative methods, however I think I lack the basic understanding of how to make this work. Any help would be appreciated.
This is the code I have used, shortened to 3 pairs instead of 20:
<span ng-bind="client['q1'].price * client['p1'].price + client['q2'].price
* client['p2'].price + client['q3'].price * client['p3'].price"></span>
This is the existing controller:
angular
.module('client')
.controller("ShowController", ($scope, Client, supersonic) ->
$scope.client = 0;
$scope.showSpinner = true
$scope.dataId = undefined
_refreshViewData = ->
Client.find($scope.dataId).then (client) ->
$scope.$apply ->
$scope.client = client
$scope.showSpinner = false
supersonic.ui.views.current.whenVisible ->
_refreshViewData() if $scope.dataId
supersonic.ui.views.current.params.onValue (values) ->
$scope.dataId = values.id
_refreshViewData()
$scope.remove = (id) ->
$scope.showSpinner = true
$scope.client.delete().then ->
supersonic.ui.layers.pop()
)
I think you are overloading (in the linguistic sense, not the coding sense) ng-bind. Doing all of that code in your HTML is messy and is not what it was created for. You would be better off doing the math in your controller, and then referencing it in ng-bind. You have only 3 pairs here, but you say you have 20, and could be more, so do it that way:
<span ng-bind="totalPrice"></span>
And in your controller:
var setTotalPrice = function() {
var ret = 0, i, maxClient = 6, client = $scope.client; // or however else you keep track of them
for (i=1;i<=maxClient;i++) {
if (client['q'+i] && client['q'+i].price && !isNaN(client['q'+i].price) &&
client['p'+i] && client['p'+i].price && !isNaN(client['p'+i].price)) {
ret += (client['q'+i].price * client['p'+i].price);
}
}
$scope.totalPrice = ret;
};
$scope.setTotalPrice = setTotalPrice;
setTotalPrice();
Just call setTotalPrice in your controller whenever you want, or on an ng-click.
Please don't abuse ng-bind for calculations! Instead calculate the values in your controller and bind the resulting value.
Problem with your code is -if any of the values is not a number your result becomes NaN. In the controller function you check for the presence of value and then operate. You may want to check whether the value is non-null as well as a number string and then operate on it.

AngularJS mathematical calculations with ng-model, ng-click buttons, outputting the results in web app

I'm starting with AngularJS, learning it bit by bit. For several days now, I am trying to understand how to calculate between ng-models (scopes) in a web-application.
Here is what I am trying to solve as an example: http://plnkr.co/edit/M2b7GNcbgM30phLXT8pU There, if I buy a stack (one click on the 1st button) of papers, then I sell 2 sheets (2 clicks on the 2nd button), the result should show "You have 1.998 stacks"
HTML
First we buy a stack of papers…
<button input type="button" class="btn" name="StackOfPaper" ng-model="StackOfPaper" ng-click="StackOfPaper = StackOfPaper + 1" ng-init="StackOfPaper=0">Buy a stack of papers<br>(total bought: {{StackOfPaper|number:3}})</button><br><br>
…then we sell individual paper sheets…
<button input type="button" class="btn" name="PaperSheet" ng-model="PaperSheet" ng-click="PaperSheet = PaperSheet + 1" ng-init="PaperSheet=0">Sell a paper sheet<br>(total sold: {{PaperSheet}})</button><br><br>
…get the number of sheets left in the stacks<br>
You have {{whatsleft|number:3}} stacks
JS
var app = angular.module('paper', []);
$scope.$watch(function() {
return Math.floor($scope.StackOfPaper*1000 - $scope.PaperSheet);
}, function(SheetsLeft) {
$scope.SheetsLeft = whatsleft;
});
Could you please help me, or suggest a good tutorial for this, I have tried all the combinations other people used, read tons of how-tos, but nothing worked for me. Maybe I am omitting some small things there?
Or is there a better way of doing the said things?
Thank you for the explanation.
Here is an updated plunker.
Since you are doing all of your scope stuff in the template right now. The easiest solution is to do the math inline:
You have {{StackOfPaper - (PaperSheet * .001)|number:3}} stacks
However, it is probably best (because you can test it easier) to have a controller that allows you to work on your model separate from the view.
Here is a second plunker.
With the controller:
app.controller('Ctrl', function($scope) {
$scope.total = 0;
$scope.stacks = 0;
$scope.soldSheets = 0;
$scope.calculateTotal = function() {
$scope.total = $scope.stacks - ($scope.soldSheets * .001);
}
$scope.addStack = function() {
$scope.stacks = $scope.stacks + 1;
$scope.calculateTotal();
}
$scope.sellSheet = function() {
$scope.soldSheets = $scope.soldSheets + 1;
$scope.calculateTotal();
}
});
This cleans up the HTML:
You have {{total|number:3}} stacks
Since Davin's answer should solve your problem, I just want to clarify some things. $scope.$watch is useful to watch for scope changes and trigger some action when a certain variable is changed eg:
$scope.someVar = 0
$scope.foo = function(){
$scope.someVar += 1
}
$scope.$watch('someVar', function(newValue, oldValue){ /* do some magic */ })
Business logic should be done by controller functions. Try not to do it in the view.
See this answer to understand how things works in Angular.

ng-grid gridData only refeshing sometimes

I have an ng-grid that I am trying to fill with some search results.
The queries work as long as the result has a different amount of rows...
If a query has the same amount of rows as the previous query... the result will not show.
// after a rest call
// we have the result
// #1 Clean gridData and local array
$scope.gridData.length = 0;
_labels.length = 0;
// #2 feed the grid
// these two functions add rows to the grid + data to the rows (and my local array)
addLabels(result.Labels);
addPhrases(result.Phrases);
// Second version attempting to call $scope.$apply
// does not work either
$scope.gridData.length = 0;
if (!$scope.$$phase) {
$scope.$apply();
}
_labels.length = 0;
addLabels(result.Labels);
addPhrases(result.Phrases);
if (!$scope.$$phase) {
$scope.$apply();
}
here is a plunker that demonstrates this:
http://plnkr.co/edit/gSFtuL?p=preview
note that if you push one more (or one less) item, the grid refreshes
It seems that ngGrid specifically watches the array.length before it passes in the options.data. Copied from here:
$scope.$parent.$watch(options.data, dataWatcher);
$scope.$parent.$watch(options.data + '.length', function() {
dataWatcher($scope.$eval(options.data));
});
I can't explain why, but it works for me if I assign an empty array in stead of the resetting the arrays length. Like this:
$scope.changeData = function() {
/* $scope.myData.length =0; -- this doesn't work */
$scope.myData = [];
$scope.myData.push({name: "aMoroni", age: 51});
$scope.myData.push({name: "bMoroni", age: 52});
$scope.myData.push({name: "cMoroni", age: 53});
$scope.myData.push({name: "dMoroni", age: 54});
$scope.myData.push({name: "eMoroni", age: 55});
};
I added this as an issue on ng-grids github site. It turned out that others had had the same problem, and that there is a solution:
(you need to get your hands dirty though and alter the ng-grid code.)
https://github.com/angular-ui/ng-grid/issues/632
Im guessing it will make it into version 3.0

Resources