Two way binding with AngularJS select for a child object - javascript

I have an angular form with this select element:
<select ng-model="expense.category" ng-options="category.code for category in categories"></select>
where expense is my object to create/edit in the form and expense.category points to child object category (with properties "id" and "code"), which is to be selected by this control.
This all works perfect for creating new expenses - category gets saved just the way it should be. When loading an expense for editing everything is fine also - the expense has its category in the controller. But on the form the select does not get pre-filled with the correct category-entry, it is empty, although all the categories are loaded into the select element. So binding works only one way (from form to model) but not in the other way (from model to form) for the select field. All the other fields (properties of expense) get filled correctly into the edit form though.
Any ideas how to solve this?

The expense.category model must be an existing item in the categories array.
What you can do to make this happen is to search for the right item in the array (with the help of the code property) and replace your existing copy of the category with the reference in the categories array.
This could be done like this:
myApp.controller('ctrl', function($scope) {
$scope.categories = [{code: 'sport'}, {code: 'music'}, {code: 'culture'}];
$scope.expense = {category: {code: 'sport'}};
function replaceWithReference(expense) {
var code = expense.category.code;
for(var i=0; i < $scope.categories.length; i++) {
if($scope.categories[i].code === code) {
expense.category = $scope.categories[i];
return;
}
}
}
replaceWithReference($scope.expense);
});
Here is the working jsfiddle-demo

Comparison between ng-options and ng-model is done by reference, not value. I suspect your expense.category, when loaded, is a separate entity from the ones in the categories array.
You should probably populate your expense.category with a category from categories.
Another option would be to replace the select with a dropdown. This would represent a bit more code (you would have to handle the binding yourself on the ng-click) but would give you more control on comparison and display.

Related

Angularjs Select with group and initial value

I have a form that includes a SELECT element. I load the possible values in my controller from a function that returns the data from a database. I want to group the options by a group name.
I have the list of options loading properly showing the grouping. My problem, is the initial value is not displaying - this is based on a data model. It shows as blank. If I choose any option in the list, it does properly display.
I followed the example from this solution Populate a Dropdown list by grouping using AngularJs
From the various other examples that I have seen, this should work...I'm guessing it is some little thing I accidentally overlooked.
This loads the possible values for the drop down:
$http.get("api/getIndustry").success(function(data){
$rootScope.industryData = [];
$.each(data, function (i, data) {
$rootScope.industryData.push({
group: data.group,
id: data.id,
text: data.text
});
});
});
For now, I am trying to initially set a selected value (eventually it will be set from reading a record):
$scope.example3model = {group: 'Energy and Natural Resources', id: '25', text: 'Utilities'};
And this is a portion of my view.
<td colspan="4" ng-hide="editableForm.$visible">
<select ng-model="example3model" class="form-control input-md" ng-options="industry.text group by industry.group for industry in industryData" >
</select></br>
{{example3model}} <- did this to see what was chosen
</td>
I'm not sure what else to try to get this to work...the only problem I see right now is that the list is not showing the 'default' value of what is initially in example3model (so the list shows as blank). If I choose a value in the list it is displayed correctly.
Any suggestions would be greatly appreciated.
The problem is that you're trying to set the initial value to an object literal, and even though it may look the same as one inside the select options it is not.
This happens because of how Javascript and AngularJS both work to set that initial object-value (note that this wouldn't happen if options was an array of primitives such as numbers and strings): {} and {} look the same from a human perspective, but they're clearly not the same in JS, try doing this in the browser console:
{} == {}
// this will be false
{ a: 1 } == { a: 1 }
// this will be false as well
Now, what Angular does behind the scenes is checking if the ngModel matches any reference inside ngOptions, that's why we need to set the initial value specifically referenced from the options array.
The initialization, in your example, must be something like this in the specific case you provided (note that I'll be hard-coding the id to match the needs of your post, but you could change it to match whatever you need)
const defaultId = 25;
$scope.example3model = $rootScope.industryData.find(data => +data.id === defaultId)
Now the ngModel value is pointing to the referenced array object that we want.
* Take a look at the official documentation about complex models for ngOptions
[note that this will not work if none of the objects in the ngOptions array has that defaulted id as it will not match any of them]

How to dynamically generate X amount of new <input> based on the value of a <select> (within Angular2)?

I'll try to make this as simple as possible.
Problem:
I've got a page where I need to have X amount of <input> tags generated depending on the value chosen in a <select>.
Stack/Tech: Angular2/Javascript
Example:
The dropdown asks, "How many kids do you have?"
If the user selects 3 in the <select> dropdown, then 3 total <input> fields are generated and inserted in the <div> right after the question.
What I've Tried:
I've successfully pulled it off already but it's semi-unreliable and I don't feel like this is the "proper" way to do things.
Failure 1:
I tried using *ngFor with conventional for loop logic. I just did something like this:
for(let i = 0; i < selectValue; i++)
It didn't like that. Apparently *ngFor is mostly used to iterate through arrays and objects and can't be used with conventional for logic.
Failure 2:
I tried to pre-generate an array of objects, a multidimensional array, and a JSON-style object with empty but pre-filled fields.
My goal was to generate X objects in an array where X corresponds to the select number that the user chose. Then the *ngFor could iterate through this "pre-filled" array, display the inputs, and then I would fill that object later to be sent over HTTP.
Basically when the <select> changes it would call a function while passing the number the user chose. That function would generate a new array with X amount of objects.
No idea why this didn't work.
Success (but isn't ideal):
I tried binding [innerHTML] = generatedHTML and then filled the generatedHTML variable with traditional Javascript. This made <input> that seemed to worked fine, but this seems like a HORRIBLE way to do this. Plus I need to create event binders on each of these new <input> and I'm not sure if manually inserted [innerHTML] will work properly within Angular2's zones if I insert it in such a terrible fashion.
Bonus Question
After getting the inputs added I'd like to access their values in the easiest and most complete way possible. Is it possible to add multiple data inputs to ONE ngModel and then access each individual input through iteration? Or do I need to figure out another way to do this? Could I just put all those inputs under one <form> and then tie that <form> to a single data model?
Thanks for the help everyone! Pretty new to Angular 2 but digging it a lot so far :)
I would do it like this:
Assign a ng-change function to your select
Process the ng-model bound value of the select in that function (to create a new Array with n-Elements)
Add whatever info to each inputArray element you need later to decorate the input boxes
Do a ng-repeat over that array to create your input's
HTML:
<select [(ngModel)]="number" (ngModelChange)="nrChanged()">
<option *ngFor="let nr of numbers" [value]="nr">{{nr}}</option>
</select>
<input *ngFor="let inputInfo of inputArr" type="text" placeholder="text" />
Component:
private numbers = [1,2,3,4,5,6,7];
private number;
private inputArr = [];
nrChanged()
{
this.inputArr = [];
for(var i = 0; i < this.number; i++){
/*here you can assign any information you need for creating the concrete input */
this.inputArr[i] = {neededInfo: 'I am the ' + i + 'Element'};
}

Write var in Angular: See which array is selected

I have a json array. I can see the item in one select. When I click on one item, a new select is create with - if there are - subarray items.
HERE can see what I am talking about.
All works fine, but I am not able to display info about the selected item.
How can I do? Because in internet I've found that you can do it writing the ng-model of the selection. But if I write:
<span>{{select.id_1}}</span>
I see just the ID of the selected item.
Thank you in advice!!
ng-model will contain the value of the selected option. So you will have to manually work out / fetch the right object depending on the value. One way to do what you want would be to go through the list and match up the id, assign that to a variable and use that instead.
I've updated your plunkr code here.
Essentially, added:
$scope.selectedProduct = {};
$scope.selectedLot = {};
....
$scope.selectedProduct = $scope.list.products[i];
....
$scope.selectedLot = $scope.Subarray[i];
This picks the item from list and subarray and assigns them so you're holding the selected object.

How to bind Json list item to pagination in angularJs?

I have large list of Json data and i arranged it in my page. Also i used paging for this. I am getting Json list in my page but the problem is, with the list generated. I have categories like(Animation, Comedy, MiniSeries, etc). When my page loads first time, listbox shows (All)- then everything is fine but when i select categories in the listbox, then the data is not displaying properly.
Each page should show pageSize(4) which is limited. this works fine when "All" is selected.
Other list items(Animation, Comedy, MiniSeries, etc) are not getting limited to pageSize properly and paging length is not reducing for
this categories.
Especially when i select "Horror", it is not displaying anything. pls help.
plnkr link
pls wait for few seconds if json data is not loaded.
// calculate page in place
$scope.groupToPages = function () {
$scope.pagedItems = [];
for (var i = 0; i < $scope.filteredItems.length; i++) {
if (i % $scope.pageSize === 0) {
$scope.pagedItems[Math.floor(i / $scope.pageSize)] = [ $scope.filteredItems[i] ];
} else {
$scope.pagedItems[Math.floor(i / $scope.pageSize)].push($scope.filteredItems[i]);
}
}
};
The issue in this case is a confusion between the various selection methods.
If you look on page 6 when 'horror' is selected, you should see "Lost Girl" -- the horror movie.
Basically, the $scope.filteredItems is the list of items selected by the filter field. If you type "Lost" into that field while "horror" is the genre, it will seem to work. That is because, when nothing is typed into the "filter" field, you are searching all the items. Then, when you find an item on (say) page 6 -- it gets added to the $scope.pagedItems array as an item on page 6.
In order for this function to work, you need to modify the $scope.filteredItems list to only select elements that are in the selected genre and meet the filter criterion.

How can I bind the selected text of a Select box to an object's attribute with Knockout JS, or anything else?

I have a select box pull down that I'm populating with a JSON list returned from a stored procedure, but unfortunately when I update the linked object I need to return the selected text of the pulldown, not the selected index like one would think (poor database design, but I'm stuck with it for now and cannot change it).
Does anyone have any ideas what I can do to keep the selected text synced with the appropriate javascript object's attribute?
You could keep both, the value and the text, if you use subscribers.
For instance, if each of your javascript objects look like this:
var optionObject = {
text:"text1"
value: 1
}
Then your binding would look like:
Where 'OptionsObjects' is a collection of optionObject and selectedOption
has two observable properties: text and value.
Finally you subscribe to the value property of the selectedOption:
viewModel.selectedOption.value.subscribe(function(newValue){
var optionText = viewModel.OptionsObjects[newValue].text;
viewModel.selectedOption.text(optionText);
});
Then if you want to see the new selected option text when the value is changed,
you could have a binding as follows:
<span data-bind:"text:selectedOption.text"></span>
In your particular case you would return selectedOption.text().
So yes, you got what I was getting at. Use the text as the value for the select options rather than using an index. The value really should be something useful, I can't think of any case where I've ever used an index. A number sure, but a number that relates to the application's models in some way (like an id from a database), not to the number of items in the select box.
Well done.

Categories

Resources