In Angular loops (ng-repeat, ng-options) you can use the following syntax:
item as item.label for item in items
Can someone please explain what each of the tokens in the expression is doing there and what it means? Can you point me to the documentation of this? I can't figure out what to search for (searching for 'as' or 'for' is useless). It is not mentioned in the documentation for ng-repeat or ng-options.
I know that somehow it lets you pick an object from a list of objects, but 'item' appears in the expression twice and it is not clear to me what the role of that token is in this expression.
Sorry if this is all documented some place which I can not find....
You have an array "items". And you are interating through it with
item in items
As the example you have copied incompletely from this page "https://docs.angularjs.org/api/ng/directive/ngOptions" would normally create a dropdown, there would be now the problem that the "item" object you currently have in your iteration has more fields than just a string to show as label for your dropdown entry. Here is the object again:
$scope.items = [{
id: 1,
label: 'aLabel',
subItem: { name: 'aSubItem' }
}, {
id: 2,
label: 'bLabel',
subItem: { name: 'bSubItem' }
}];
So what do you want to show then? Yeah, you want to show "item.label".
And thats what
item as item.label
does. It tells the loop to use the current "item.label" value as "item" for this specific loop.
Related
I have the following two datasets:
// This is based on what user input, usually they will just select 1 or 2.
var selected = ['alpha', 'bravo', 'charlie', 'delta', ...]
var list = [
{
"properties":{
"name" : "Example Name",
"tags":{
"multi_select":[
{
"name":"alpha"
},
{
"name":"charlie"
}
]
}
}
},
...
// same same but many more objects with different tags each
]
Yes, it's super nested, but it comes from an API...
Anyway, so what I'm trying to do now is filter() it based on the selected terms, example,
If list tags contain "alpha, charlie" and "alpha" is selected, it would display object, but if "alpha and bravo" is selected, the object won't match.
So with fewer it can match, but when it gets narrowed don't it shouldn't match.
I tried the following:
let res = list.filter(hb => hb.properties.Tags.multi_select.some(tag => tag.name.includes(selected)));
console.log(res);
This code is largely from Javascript array.filter by element in children
So I noticed, if I have selected "charlie" it returns the correct objects(s) that contains tag "charlie", but when I select more than 1, eg "charlie" and "alpha" it returns nothing even though there is suppose to be matching objects.
Any suggestions how I could improve the filtering?
Thanks in advance.
You need to check every selected tag agains some name of the filtering object.
let res = list.filter(hb => selected.every(tag =>
hb.properties.tags.multi_select.some(({ name }) => name === tag)
));
I think the argument of your includes method here shouldn't be an array here, the check should rather be other way around.
let res = list.filter(hb => hb.properties.tags.multi_select.some(tag => selected.includes(tag.name));
Also, one suggestion - It might be more optimal to use Set instead of Array for this use case maybe, if you want to research a bit.
As to why your code is working when you are only having one item, the reason is that the selected array undergoes a string conversion so ["charlie"] becomes charlie which is returned as true as charlie is your first element in the multiselect array. But when you add another item, it happens like this ["alpha", "charlie"] is casted to "alpha,charlie" which is concatenation of two items, so it would naturally return false.
My project currently has a list of Comments stored in an array called feedbackList.
Let's say that each comment contains a Author and Feedback string.
So, my feedbackList would be something like;
feedbackList: [
{name: 'John', comment: 'a nice comment'},
{name: 'Steve', comment: 'first comment'}
]
These are then displayed using some code such as;
<div class="comment-wrapper" v-for="(comment, index) in feedbackList" :key="index">
<CommentCard
v-bind:author="comment.author"
v-bind:comment="comment.comment"
v-on:DeleteComment="DeleteComment($event)"
/>
</div>
Then, I use a function to submit a new comment, containing this;
const newcomment = {
author: this.author
comment: this.feedback,
timePosted: moment().format()
}
this.feedbackList.unshift(newcomment)
However, the issue is as I push the new comment to the beggining of the array, my comments shuffle down in an odd way.
I will now appear as the person below me (author), but with my own comment.
The post below that will take on the one before that, with the previous author but their own comment, and so on and so on.
If I use .push(newcomment) it adds the new comment to the bottom as intended. However, then comes the issue of deleting a comment, making the previous one now take over the recently delete author, but their own comment.
I know this is a little confusing, but essentially upon updating my array, it seems the v-for is not updating the attached data correctly and giving the correct comment but previous author.
That's because your index is binded as a key. Try adding an id variable (unique for every comment, it can be an incrementing number or a random string) and use that as your key in v-for.
Example:
feedbackList: [
{id: 'nlgvn5d6', name: 'John', comment: 'a nice comment'},
{id: '85m18efd', name: 'Steve', comment: 'first comment'}
]
Remeber that when adding a new item to the list it also needs to have its own unique id.
Then you can use it like this:
v-for="comment in feedbackList" :key="comment.id"
So I have a controller like this
app.controller("ReservationCtrl", function($scope) {
$scope.rooms = [{id: 1, name: "Room 1"}, {id: 2, name: "Room 2", }];
$scope.reservation = {id: 1, time: "9AM", room: {id: 2, name: "Room 2"}};
});
A view that looks like this:
<select ng-model="reservation.room" ng-options="room as room.name for room in rooms"></select>
The problem is that the select box won't bind to the correct room unless I say
$scope.reservation.room = $scope.rooms[1];
This is rather inconvenient for development as the room is not the only field on the reservation model that needs to be bound to a select box. How can I apply the binding without doing this extra step?
Also, the binding breaks again if I do something like
$http.get("/reservation/2").success(function(data) { $scope.reservation = angular.copy(data); });
I think this is because,
in your ng-options if you select an option, the selected value is an object
EX: if you select first option then model value is an object which is the first element in the $scope.rooms ({id: 1, name: "Room 1"}).
objects are reference type data type. So what it does is if the objects create one time then all of its usage are pointing to that object. have a look at this article.
So your selected value is an object which is a reference type. In your case you have two independent objects for the $scope.reservation.room and the {id: 2, name: "Room 2"} which is in rooms array. note that they are in separated memory slots.
In your working case both of the $scope.reservation.room and the {id: 2, name: "Room 2"} which is in rooms array are pointing to same memory slot because you have equals the two as $scope.reservation.room = $scope.rooms[1]; This means $scope.reservation.room & $scope.rooms[1] both are pointing to the same object in the memory.
To work this out you can do something different than your working solution.
change the ng-repeat as following
...ng-options="room.id as room.name for room in rooms"...
and change the ng-model
....ng-model="reservation.id"....
this will select the id of the option as the selected value for EX if you select the first option then the model value will be 1 which is the id of the first option.
in this case selected model values are primitive(like 1,2,3..) type data then its not going to search for the objects in memory instead it will get the value of stack and check with the option values and select the correct one.
here is a DEMO this will select the second option initially.
------------------------------------------------- SUMMARY -------------------------------------------------
If the selected model value is an object then it will check the memory address of the selected object and the all objects of the $scope.rooms and check if there is a matching element and select the matching option if one is found. if no one found then nothing will select.
If the model values are primitive like 1,2,3.. it will search the value and check if there any matching option value if one is found it will select that option.
So it seems the key to this problem is the track by clause in the ng-options directive. See my updated fiddle
As you will see, the whole model is being updated and not just its ID. The documentation says that select as and track by were not meant to be used together, but the example they used to illustrate this is a bit different from mine.
Since I've received no feedback from the official Angular channels on this matter to date, I'm gonna mark this as solved and move on.
Thanks for the help everyone.
When the view is loaded, the select box has no selected value because the room object in $scope.reservation is not the same object as the one in $scope.rooms, event if it has the same values.
Thats why your example does not work (fiddle)
But this one works:
$scope.rooms = [{id: 1, name: "Room 1"}, {id: 2, name: "Room 2"}];
$scope.reservation = {
id: 1,
time: "9AM",
room: $scope.rooms[1] // <-- now the reservation room references a valid ng-repeated room
};
See updated fiddle
To avoid problems, I'll suggest you bind to the variable of your select box only the room id of the reservation. Because it is a primitive type, the comparison will be made by value, and that will solve also your second use case. Moreover, you better not duplicate data in the reservation object. See fiddle
If you need to display the name of the reserved (selected) room, you could easily write a getRoomById function that will look into the array of rooms.
Change the scope only when the value changes.
if($scope.reservation.room != $scope.rooms[1])
$scope.reservation.room = $scope.rooms[1];
I have the following HTML:
<div class="row" ng-repeat="question in currentTemplate.questions">
<div class="col-xs-4" ng-show="editingQuestion[$index]">
<select style="width:100%;" ng-model="editingCurrentlySelectedAnswerOption[$index]"
ng-options="answerOption as answerOption.back for answerOption in answerOptions">
</select>
</div>
</div>
I am wondering how I set the value of what is shown in the select statement. When the page loads, it should already be set, because I go through a for loop to set the values of editingCurrentlySelectedAnswerOption so that each one is preselected (the [$index] is referring to the index of an ng-repeat which this is inside of. Since there are multiple selects inside the ng-repeat, I need one spot per ng-repeat in the array to keep track of each individual selection), but instead it comes up as a blank spot first. I have used ng-init, a function that is called whenever a button is pressed which unhides the above div, and a function when the page loads. I have used bindings in the DOM and console.logs to check the values. They all seem to be correct. answerOptions in the ng-options is:
var answerOptions = [
{ front: "RadioButton", back: "Multi-Choice" }
{ front: "CheckBox", back: "Multi-Answer" }
{ front: "TextBox", back: "Free Text" }
];
When I print what editingCurrentlySelectedAnswerOption[$index] is it always comes up correctly showing an object just like one of the above objects, but for some reason it is always a blank select statement when it loads. Is there something I don't know about how ng-options work with objects? Or am I doing something else wrong?
UPDATE:
I am setting the values of editingCurrentlySelectedAnswerOption this way:
this.scope.editingCurrentlySelectedAnswerOption = [];
for (var i in this.scope.currentTemplate.questions) {
if (this.scope.currentTemplate.questions.hasOwnProperty(i)) {
this.scope.editingCurrentlySelectedAnswerOption.push(this.scope.currentTemplate.questions[i].answerType);
}
}
the reason I am incrementing the amount of times there are questions in this.scope.currentTemplate is because in the html the ng-repeat repeats on the amount of questions as well. They should match up.
and this is how this.scope.currentTemplate.questions[0] is defined:
new Utilities.Question(
"Question1",
{ front: "TextBox", back: "Free Text" },
["Yes", "Maybe", "No", "I don't know"],
[50.0, 25.0, 0.0, -25.0]
)
and here is the definition of a Utilities.Question:
export class Question {
question: string;
answerType: any;
answerOptions: string[];
answerWeights: number[];
constructor(question?: string, answerType?: any, answerOptions?: string[], answerWeights?: number[]) {
this.question = question;
this.answerType = answerType;
this.answerOptions = answerOptions;
this.answerWeights = answerWeights;
}
}
Everyone was correct that ng-model/ng-options refer to the reference of an object and not the value, so even though the two object's value were correct the references were not the same and, so, the select didn't see the ng-model variable as equal to the options given. To fix this I just added a track by to the end of my ng-options and it works now. Not sure if this is the best way to do it, but it works! Thanks for everyone's help!
I have looked what seems everywhere for this but maybe it is too obvious and no one mentions it but I am making a windows 8 app and I want the user to be able to edit the data.
Lets say im trying to make a To-Do list, I want the user to be able to add entries. For my current app with dummy static data I have these entries stored in an array in a javascript file. Should I just make the user be able to add/edit/remove entries in the array or is there a different method I should use?
If someone could link me some material to read or an example that shows what I am looking for it would be really helpful.
It really depends on the JavaScript framework you're using. Assuming that you're using Microsoft's own WinJS, what you're looking for is "data binding". This allows you to bind an object or an array to a "control" and have it be automatically updated. Then you just work with the array, adding, removing, sorting items. The data-bind automatically reflects those changes in the template.
Here is a quick example of data-binding from MSDNs own documentation:
<div id="listDiv"
data-win-control="WinJS.UI.ListView"
data-win-options="{ itemDataSource : dataList.dataSource }">
</div>
<script type="text/javascript">
var dataArray = [
{ name: "Marley", species: "dog" },
{ name: "Lola", species: "cat" },
{ name: "Leo", species: "dog" },
{ name: "Izzy", species: "cat" },
{ name: "Ziggy", species: "cat" },
{ name: "Ruby", species: "dog" }
];
var dataList = new WinJS.Binding.List(dataArray);
WinJS.UI.processAll();
</script>
You can see more here: http://msdn.microsoft.com/en-us/library/windows/apps/hh700774.aspx
Each JavaScript framework does this a little bit differently. For example, AngularJS would let you bind an array to a list like this:
<ul>
<li ng-repeat="item in list">{{item.name}}</li>
</ul>
Where 'list' is the name of an array of objects in your scope, item is a single object in the array, and item.name is a string with the name of a single object. The syntax is different, but once again, you would simply manipulate the array and it would be reflected in the resulting HTML.