I'm trying display some data loaded from a datastore and it's not reflecting changes on the UI. I created an example to show a general idea of what I'm trying to achieve.
http://plnkr.co/edit/MBHo88
Here is the link to angularjs example where they show when on click then dropdowns are clear out. If you replace the expression with one of the colors of the list dropdowns are well selected. Does this type of selection only work on user events?
http://docs.angularjs.org/api/ng.directive:select
Help is appreciated!!!
Actually the problem is that ngSelect compares objects using simple comparition operator ('=='), so two objects with same fields and values are considered as different objects.
So you better use strings and numbers as values ('select' parameter in expression of ngSelect directive).
Here is kind of solution for your plunker.
Aslo there are some discussion about this topic on GitHub:
https://github.com/angular/angular.js/issues/1302
https://github.com/angular/angular.js/issues/1032
Also as I headred there is some work in progress about adding custom comparor/hashing for ngSelect to be able to use ngSelect more easier on objects.
One mistake in the initialization of your controller. You have to refer to the objects in your palette, since these are watched on the view:
$scope.selectedColors.push({col: $scope.palette[2]});
$scope.selectedColors.push({col: $scope.palette[1]});
Same with your result:
$scope.result = { color: $scope.obj.codes[2] };
Then you need to watch the result. In the below example, select 1 receives the value from the initiating select, the second receives the value below in the palette. I don't know if that's what you wanted, but you can easily change it:
$scope.$watch('result', function(value) {
if(value) {
var index = value.color.code -1;
$scope.selectedColors[0] = {col: $scope.palette[index] };
$scope.selectedColors[1] = {col: $scope.palette[Math.max(index-1, 0)] };
}
}, true);
See plunkr.
Ok, I think I figured this out but thanks to #ValentynShybanov and #asgoth.
According to angularjs example ngModel is initialized with one of the objects from the array utilized in the dropdown population. So having an array as:
$scope.locations = [{ state: 'FL', city: 'Tampa'}, {state: 'FL', city: 'Sarasota'} ....];
And the dropdown is defined as:
<select ng-options="l.state group by l.city for l in locations" ng-model="city" required></select>
Then $scope.city is initialized as:
$scope.city = $scope.locations[0];
So far so good, right?!!!.. But I have multiple locations therefore multiple dropdowns. Also users can add/remove more. Like creating a table dynamically. And also, I needed to load data from the datastore.
Although I was building and assigning a similar value (e.g: Values from data store --> State = FL, City = Tampa; Therefore --> { state : 'FL', city : 'Tampa' }), angularjs wasn't able to match the value. I tried diff ways, like just assigning { city : 'Tampa' } or 'Tampa' or and or and or...
So what I did.. and I know is sort of nasty but works so far.. is to write a lookup function to return the value from $scope.locations. Thus I have:
$scope.lookupLocation = function(state, city){
for(var k = 0; k < $scope.locations.length; k++){
if($scope.locations[k].state == state && $scope.locations[k].city == city)
return $scope.locations[k];
}
return $scope.locations[0]; //-- default value if none matched
}
so, when I load the data from the datastore (data in json format) I call the lookupLocation function like:
$scope.city = $scope.lookupLocation(results[indexFromSomeLoop].location.state, results[indexFromSomeLoop].location.city);
And that preselects my values when loading data. This is what worked for me.
Thanks
Related
I have an input field that is supposed to contain numbers.
It is bound to an object property.
I want input entered as 4,5 to automatically get converted to 4.5 in both model and view.
HTML:
<input data-ng-model="productContent(product.Id).Org" value="{{productContent(product.Id).Org | replaceComma}}" />
Control:
$scope.productContent = function (prodId) {
var content = $.grep($scope.productsContent, function (el) { return el.ProdId === prodId });
return content[0];}
Filter:
app.filter('replaceComma', function () {
return function (val) {
return (typeof val) == "string" ? val.toString().trim().replace(",", ".") : val
};
});
Result:
When I enter a number, at first the model (productContent) retrieves the correct object. Then the filter code is called and returns a correctly converted string. I would expect both the model and view to be updated to the filtered value, but both are updated with the unfiltered value. What am I doing wrong?
I have faced the same problem in the past but instead of creating my own filter, I took a different path and found something ready to use instead.
angular-input-masks by assisrafael one of my favourite angular extensions for this purpose:
https://github.com/assisrafael/angular-input-masks
Examples:
http://assisrafael.github.io/angular-input-masks/
Since the author has written the documentation, I don't want to get extensive on it and be outdated in the future. As a quick reference, look for ui-number-mask.
Maybe this is not a direct answer to your question, since it's not replacing commas with periods, but making you type the decimals instead.
On a side note, you can suppress the thousands separators with ui-hide-group-sep
I hope that's helpful, otherwise leave a comment and I'll be happy to continue to assist you!
-Helvio
Programmatically
So I am facing an issue with the select in Angular JS. This is a follow-up question to my previous question
Now I am able to load my values in the second select but I am unable to write the same value to it when the query from DB comes back. So If I select Ford in one select and Figo in another. and press save. The values go into DB correctly but when I come again to that view. Shouldn't that value persist right? I mean I should be able to see figo in the second select. But I am unable to. I tried setting to the ng-model but that method doesn't work. I have also attached a fiddle with the proper comments as to what is not working.
The code uses one of the marked answers from the question.
HTML
<select ng-model="carBrand" name="carBrand" required ng-options=" brand for brand in brands" ng-change="selectedCar(carBrand)"></select>
<select ng-model="carModel" name="carModel" required ng-options="model.name for model in cars[carIndex]"></select>
JS
$scope.brands = ['Ford', 'Honda', 'Hyundai', 'Mahindra',
'Maruti Suzuki', 'Nissan', 'Renault', 'Skoda', 'Tata', 'Toyota', 'Volksvagen'
];
$scope.cars[0] = $scope.cars[0] = [{
name: "Figo",
capacity: 45
}, {
name: "Ecosport",
capacity: 52
}, {
name: "Fiesta",
capacity: 45
}, {
name: "Endeavour",
capacity: 71
}];
$scope.carBrand = $scope.brands[0];
$scope.cars = [];
$scope.selectedCar = function(brand) {
$scope.carIndex = $scope.brands.indexOf(brand);
};
$scope.carModel = "Anything";
EDIT
I think there is an issue in the understanding of the question. All I want is programmatically(by code) set the value of the second select. That's it.
I have forked your fiddle. Have a look: https://jsfiddle.net/4ynkrj0o/2/
var MockSaveMyData = function(itemsToSave){
// I JUST SAVED ALL MY DATE !! :D
var savedItems = itemsToSave;
savedItems.CreatedAt = new Date();
savedItems.CreatedBy = 'YOU !!';
return {
then: function(callback){
callback(savedItems);
}
};
};
You will have to save and retrieve the values on every page refresh.
Like you said, you have cookies where you save your Car object. In that case, you will have to assign the object on the top firth thing inside the controller.
You will also have to update the cookies after you have run the save function. I will suggest doing all this in the success call back which http calls provide. You can update the cookie and then refresh the page. Or you can just assign the updated values and give a message to the user stating that the save call has been successful.
Again, in my opinion, you should avoid reloading the page. It almost defies the essence of Angular. Where you can do almost everything asynchronously.
I want to understand how to successfully set a value inside a DropDown List after it has been populated by a promise. The value to be set will be taken from another promise which will fill a form with a JSON structure.
In my particular case the incidence is as follows
The Drop-Down List is built as :
1) HTML Template (Jade)
select(name="inputUserRelationship",
ng-model="myForm.relationship",
ng-options="relationshipOption as relationshipOption.value for relationshipOption in relationships track by relationshipOption.id",
ng-init="myForm.insuredRelationship = relationships[0]")
option(value="") -- SELECT --
2) Controller:
$scope.getRelationTypes = function(){
HttpSrv.get('api/getRelationTypes/').then(function(results){
$scope.relationships = results;
}); };
The form gets filled in the Controller as follows:
$scope.getFormInformation = function(ID){
HttpSrv.get('/api/getFormInfo/' + ID).then(function(results){
if(results)
{
$scope.fillForm(results);
}
}); };
$scope.fillForm = function(filledFormData){
$scope.myForm.relationship = filledFormData.relationnshipID; };
This produces the following issues on my JS Debugging Console:
The value gets set on the model
The Drop-Down List stays on the default empty value ([0]).
When I try to change the selected option on my Drop-Down list it then produces the following JS Console Error.
TypeError: Cannot assign to read only property 'id' of 9
at setter (vendor.js:42989)
at Lexer.readIdent.token.fn.extend.assign (vendor.js:42424)
at validationErrorKey.$setViewValue (vendor.js:49629)
at vendor.js:53523
at Scope.promises.$get.Scope.$eval (vendor.js:44729)
at Scope.promises.$get.Scope.$apply (vendor.js:44827)
at HTMLSelectElement. (vendor.js:53465)
at HTMLSelectElement.jQuery.event.dispatch (vendor.js:4641)
at HTMLSelectElement.jQuery.event.add.elemData.handle (vendor.js:4309)
Any information is greatly appreciated. I have already researched & tested the $scope.apply() and $q options and neither have been successful to me even though I know they point to the right direction.
Cheers!
if your $http API call returns an json in the format:
[{"id":"1", "value":"Dropdown desc"}, {...}, {}]
You should set an object literal with the same structure to set the dropdownlist to a specific values like:
$scope.myForm.relationship = {"id":"1", "value":"Dropdown desc"};
I have an array that looks like this:
$rootScope.array = [{
"id":"a",
"title":"a",
"options":[
{
"optId":"abc",
"isInvalid":false
},
{
"optId":"efg",
"isInvalid":false
}
]
},
{
"id":"b",
"title":"b",
"options":[
{
"optId":"hij",
"isInvalid":false
},
{
"optId":"lmn",
"isInvalid":false
}
]
}];
On any change to the 'isInvalid' attribute inside 'options', I want to trigger a function that would evaluate the entire array and set some properties to 'true' and some to 'false'.
I understand something of this sort can be done using deep $watch object equivalence, but I am not sure as to how exactly I can watch only for change in the 'isInvalid' and not change in lets say 'optId'.
In the bigger scheme of things, my intent is to validate the entire array by a set of business rules. So, if the "isInvalid" attribute of the first object changes to 'true', I want to make sure that the 'isInvalid' of the second object is set to 'false' and so on.
So if there is a better way to do this other than $watch, I am all ears, because I'm relatively new to Angular and I'm a little lost!
Rather than using a deep watch (which can silently cause performance issues), create a custom function for your watch expression. The example below iterates through the array elements and options creating one check string containing each item's id, option id and its respective isInvalid value.
The check string will look something like this: "a|abcfalseefgfalseb|hijfalselmnfalse"
If an isInvalid value changes, the check string will change, and the watch will trigger.
$scope.$watch(function() {
var check = "";
for (var i = 0; i < $scope.array.length; i++) {
var item = $scope.array[i];
check += item.id + "|";
for (var j = 0; j < item.options.length; j++) {
var option = item.options[j];
check += option.id + item.isInvalid;
}
}
return check;
}, doSomething());
If your structure is not too big, a deep watch would do the trick.
http://teropa.info/blog/2014/01/26/the-three-watch-depths-of-angularjs.html
If you are concerned about performance, and depending your scenario, you could evaluate doing something like:
Having a parent/global isInvalid flag, whenever a flag is marked as invalid mark as well the global invalid, then you only have to watch for the global invalid flag and once the flag is invalid traverse to find the not valid element.
Other options that come to my mind could be to add a watch per entry in the array, but not sure if this could have worse performance than the deepwatch solution.
If you don't make a deepwatch abuse, it can be a good solution for your scenario.
If you don't want to use a deep watch. You can define your own watch logic as well.
According to angular document. In $watch(expression) expression function will be executed multiple times during $digest.
So you can use you own code to detect the change in your huge array.
live demo: http://plnkr.co/edit/X4J5Faoa2ni79RmFa3co?p=preview (open your console to see the log).
I have a drop-down list
<select ng-model="referral.organization"
ng-options="key as value for (key, value) in organizations">
</select>
where organizations is filled using a $http request. I also have a resource referral which includes several properties, including an integer organization that corresponds to the value saved in the drop-down. Currently, the drop-down works fine and selecting a new value will update my referral resource without issue.
However, when the page loads the drop-down is blank rather than displaying the value of referral.organization that was retrieved from the server (that is, when opening a previously saved referral form). I understand that this is likely due to my resource being empty when the page first loads, but how do I update the drop-down when the information has been successfully retrieved?
Edit:
{{ organizations[referral.organization] }} successfully lists the selected value if placed somewhere else on the page, but I do not know how to give the tag this expression to display.
Second Edit:
The problem was apparently a mismatch between the key used in ngOptions and the variable used in ngModel. The <select> option's were being returned as strings from WebAPI (despite beginning as Dictionary) while the referral model was returning integers. When referral.organization was placed in ngModel, Angular was not matching 2723 to "2723" and so forth.
I tried several different things, but the following works well and is the "cleanest" to me. In the callback for the $resource GET, I simply change the necessary variables to strings like so:
$scope.referral = $resource("some/resource").get(function (data) {
data.organization = String(data.organization);
...
});
Not sure if this would be considered a problem with Angular (not matching 1000 to "1000") or WebAPI (serializing Dictionary<int,String> to { "key":"value" }) but this is functional and the <select> tag is still bound to the referral resource.
For simple types you can just set $scope.referral.organization and it'll magically work:
http://jsfiddle.net/qBJK9/
<div ng-app ng-controller="MyCtrl">
<select ng-model="referral.organization" ng-options="c for c in organizations">
</select>
</div>
-
function MyCtrl($scope) {
$scope.organizations = ['a', 'b', 'c', 'd', 'e'];
$scope.referral = {
organization: 'c'
};
}
If you're using objects, it gets trickier since Angular doesn't seem smart enough to know the new object is virtually the same. Unless there's some Angular hack, the only way I see forward is to update $scope.referral.organization after it gets loaded from the server and assign it to a value from $scope.organizations like:
http://jsfiddle.net/qBJK9/2/
<div ng-app ng-controller="MyCtrl">
<select ng-model="referral.organization" ng-options="c.name for c in organizations"></select>
{{referral}}
<button ng-click="loadReferral()">load referral</button>
</div>
-
function MyCtrl($scope) {
$scope.organizations = [{name:'a'}, {name:'b'}, {name:'c'}, {name:'d'}, {name:'e'}];
$scope.referral = {
organization: $scope.organizations[2]
};
$scope.loadReferral = function () {
$scope.referral = {
other: 'parts',
of: 'referral',
organization: {name:'b'}
};
// look up the correct value
angular.forEach($scope.organizations, function(value, key) {
if ($scope.organizations[key].name === value.name) {
$scope.referral.organization = $scope.organizations[key];
return false;
}
});
};
}
You can assign referral.organization to one of objects obtained from ajax:
$scope.referral.organization = $scope.organizations[0]
I created simple demo in plunker. Button click handler changes list of objects and selects default one.
$scope.changeModel = function() {
$scope.listOfObjects = [{id: 4, name: "miss1"},
{id: 5, name: "miss2"},
{id: 6, name: "miss3"}];
$scope.selectedItem = $scope.listOfObjects[1];
};
The other answers were correct in that it usually "just works." The issue was ultimately a mismatch between the organization key (an integer) stored inside $scope.organizations and the key as stored in the $http response, which is stored in JSON and therefore as a string. Angular was not matching the string to the integer. As I mentioned in my edit to the original question, the solution at the time was to cast the $http response data to a string. I am not sure if current versions of Angular.js still behave this way.