I am trying to have a contextual state dropdown menu that is bound to the country so that only the relevant states display.
I have looked at these two answers for a solution to my implementation.
Angularjs trigger country state dependency
angularjs dependant dropdown option value
But I cannot get them to work and am missing something.
My preferred solution would use some existing ng filter, but I understand that only works on arrays and not objects
<select ng-model="user.state" ng-options="state.name for state in states track by state.id | filter:state.countryid = user.country.id">
I tried converting the object to an array, but that did not seem to work.
I could create a custom filter, but I am hoping to avoid that.
<select ng-model="user.state" ng-options="state.name for state in states track by state.id | customStateFilter:user.country.id">
It seems like there should be some way to get ng-options to work without modifying the data.
Is there an existing Angular filter that works on objects or a way to do conditional logic to filter out the objects dynamically?
You have a few issues here (in addition to a mismatch of state.countryid and country.id):
First, track by comes after the filter (or, in other words, filter comes right after the array, since it filters the array).
Second, you are right - the built-in filter filter only works on arrays, not objects. For objects, you'd need a custom filter.
And lastly, filter filter doesn't accept an expression to evaluate (filter:state.countryid = user.country.id, not to mention that this "expression" that you tried to provide doesn't compare ===, but assigns =). filter accepts as a parameter either a string - to match any property against, an Object specifying which property to match against which value, or a predicate function.
In your case, an object is what you need.
To put this thing together you get:
<select ng-model="selectedState"
ng-options="state.name for state in states | filter: {countryid: user.country.id}:true track by state.id">
<option value="">select state</option>
</select>
Related
I have a HTML fragment that iterates over key, value collection. When I create an object and put some value in, then iterate trough that object via HTML fragment, all works perfectly.
However since I need keys in specific order, I'm using a Map instead of plain object. This time when I debug I can see that my insertion order was preserved, but for some reason the HTML fragment which iterates over collection doesn't seem to know how to do so. I see nothing on my screen when I use the map object, opposed to the regular object when I see unordered content
tr ng-repeat="(key, value) in rowTitlesValues"
Is how my HTML fragment looks like, when I switch rowTitlesValues back to object works again, what am I doing wrong, and how does one keep insertion order or how do I sort object so it's keys are in custom order?
From Angular reference on ng-repeat (link):
Iterating over object properties
It is possible to get ngRepeat to iterate over the properties of an object using the following syntax:
<div ng-repeat="(key, value) in myObj"> ... </div>
You need to be aware that the JavaScript specification does not define the order of keys returned for an object. (To mitigate this in Angular 1.3 the ngRepeat directive used to sort the keys alphabetically.)
Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser when running for key in myObj. It seems that browsers generally follow the strategy of providing keys in the order in which they were defined, [...]
If this is not desired, the recommended workaround is to convert your object into an array that is sorted into the order that you prefer before providing it to ngRepeat. You could do this with a filter such as toArrayFilter or implement a $watch on the object yourself.
Additionally, I do not think Angular 1.x knows how to iterate over a Map. I believe this line in the code proves it:
collectionKeys = [];
for (var itemKey in collection) { // iterates your object using `in`, not `of` or `Map.forEach()`
...
}
// ng-repeat then iterates the collectionKeys to create the DOM
So you will probably need to act as Angular docs suggest:
[...] convert your object into an array that is sorted into the order that you prefer before providing it to ngRepeat. You could do this with a filter such as toArrayFilter or implement a $watch on the object yourself.
This code used to work just fine when I was using bootstrap's way of dropdown menus.. now that I switched to conventional I dont know why I'm getting this problem. Why can't I access the properties?? Driving me nuts here
HTML
<select class="form-control" ng-model="client" ng-change="clientSelect(client)">
<option value="">--- Please Select ---</option>
<option ng-repeat="client in clientList" value="{{client}}">{{client.clientName}}</option>
</select>
Angular/Javascript
$scope.clientSelect = function (client) {
console.log(client);
console.log(client.clientName); //is undefined
console.log(client.clientId); //is undefined
}
Ouput:
{"clientId":102,"clientName":"Testing"} //i clearly see the properties here...
UsersController.js:130 undefined
UsersController.js:131 undefined
EDIT: when I console.log($scope.clientList) it's fine.. the object of the first item in the array looks like this:
0: Object
$$hashKey: "object:254"
clientId: 102
clientName: "Testing"
_proto: Object
You can't access properties, because the client you get from ng-repeat is a string.
Check the console log of this Plunker I created with your code. You will see that the first consol.log(client) is actually logs out this string: {"clientId":102,"clientName":"Testing 1"}. Therefore, it doesn't have any properties.
From the select docs:
Note that the value of a select directive used without ngOptions is always a string. When the model needs to be bound to a non-string value, you must either explictly convert it using a directive (see example below) or use ngOptions to specify the set of options. This is because an option element can only be bound to string values at present.
Consider using the ngOptions directive instead of <select> with ng-repeat.
I have an ng-repeat set up like so:
ng-repeat="article in main[main.mode].primary | orderBy: main[main.mode].primary.filter.order
track by article.url"
main[main.mode].primary is an array and ….filter.order is a string.
According to this blog post
Behind the scenes ngRepeat adds a $$hashKey property to each task to keep track of it. If you replace the original tasks with new tasks objects from the server, even if those are in fact totally identical to your original tasks, they won’t have the $$hashKey property and so ngRepeat won’t know they represent the same elements.
Regenerating the list is a very common task and the app is hanging for over a second, hence my interest in track by. According to the many questions and docs I've looked at, I've used the correct syntax to both order and track an array. From the docs:
item in items | filter:searchText track by item.id is a pattern that might be used to apply a filter to items in conjunction with a tracking expression.
Why isn't track by being implemented? I'm running angular 1.3.11.
Edit
It doesn't even work if I remove the orderBy argument
ng-repeat="article in main[main.mode].primary track by article.url"
according to the Angular Documentation orderBy only works on arrays so if you are iterating over an object you are not going to be able to use it unless you convert your object into an array
https://docs.angularjs.org/api/ng/filter/orderBy
there are other ways you can handle this either implementing your own filter or just by transforming your object into an array of objects with key value properties. something like
var narr=[]
angular.forEach(object,function(k,v){
narr.push({key:v,value:v})
})
and now narr is an array ready to be sorted by either key or value using orderBy
I hava a selectOptions ajax-based asincronous loader; it accepts remote address and returns an observable array, correctly populated with descriptions and keyvalues to be accepted by the following binding
<select data-bind="value: selectedVal, options: opts, optionsText: 'desc', optionsValue:'key', optionsCaption: ''"/></div>
The fact is that, being asincronous, when I trigger a select options change, based on some user actions, I assign it to my model observable array, I do not get the select popuated, but remains empty.
mymodel.opts = loadOptions("<remoteaddress>");
I know when the second line is called the anwer is not arrived yet, but the returned value is an observableArray, so it should respond correctly whenever is populated, having been assigned to an observable array binded with the ui.
If I hardcode the returned object from the ajax call (when it returns) taking it from console.log in Firefox, or if I pass the observable array opts into the loadOptions, and change it to build up the opts inside it, then it works, but I really need to use loadOptions as is, asincronous. I also tried to append mymodel.opts.valueHasMutated(), but yet ko cannot use the newlly arrived observableArray.
If possible leaving intact the options loader, and if possible without using a custom binding, can I use the incoming observable array for binding when it will be ready?
The problem you've got is that when this line runs:
mymodel.opts = loadOptions("<remoteaddress>");
it's replacing the entire observable array with a different observableArray, rather than updating the current one. You need to update the existing one - can you change loadOptions to return a normal array, rather than an observable one? You can then do:
//clear any existing entries
mymodel.opts.removeAll();
//push the new entries in
mymodel.opts.push.apply(mymodel.opts, loadOptions("<remoteaddress>"));
I'm attempting to populate a select field with options using bootstrapped data. I'm encountering an issue when binding my array of models to the jQuery select object...
The HTML
<select data-each-project="projects" id="project-selection">
<option data-value="project:description"></option>
</select>
The Binding
project_array = new Array()
_.each projects, (project) ->
projects_array.push project
rivets.bind #el.select, projects:projects_array
The Result
I receive an error indicating that the object has no .on method -> which it doesn't because it is an array of models not a model it self...
How should this really be done? Thanks!
When you subscribe to an iteration binding rivets does two things:
Subscribes to the entire array so if it changes it will rerun the
iteration
Subscribe to all children of the array that need binding
Rivets isn't subscribing to your children because you are not using any bindings that require it.
project:description = non-subscribe binding
project.description = subscribe binding
If you don't want to subscribe to array changes (I think that's what you're asking for) you can do data-each-project=":projects"