How make related selects in Angular? - angularjs

I have two select list HTML.
How I can make easy them related? I mean when I select option from first select, some options from second select are shown to hidden?
Selects lists HTML created by PHP.

There are a few ways using angular. One way is to use the ng-change:
<select ng-model="selectedItem1" ng-options="item in items1" ng-change="update(selectedItem1)"></select>
and then have the update(selectedItem1) function update your items2 list. And vice versa for your items2 drop down.
$scope.update = function(selectedItem1) {
$scope.items2 = // logic to filter items 2 based on selectedItem1
}
<select ng-model="selectedItem2" ng-options="item in items2" ng-change="updateSet1(selectedItem2)"></select>
Alternatively, you could use a $watch function, to watch selectedItem1 and update items2 list in this function.
If you need to use a custom filter, see here:
https://docs.angularjs.org/tutorial/step_09
angular.module('myModule', []).filter('items2', function() {
return function(selectedItem1, items2) {
for (var i = 0; i < items2.length; i++) {
// return items you want
}
};
});
Then in your controller, include this filter as dependency items2Filter by appending Filter and you can update $scope.items2 like so:
$scope.items2 = items2Filter($scope.selectedItem1, $scope.items2);

Related

Issue with Angularjs Dropdown and a custom filter

I'm having an issue using a dropdown that is populated with ng-repeat option values or even when using ng-options.
Basically I'm pulling a list of subsidiaries from the database. I then have a dropdown to choose a company, which in turn should populate the subsidiary dropdown with subsidiaries of the chosen company. Since many of the subsidiaries are of the same company, if I try and pull the the company name in ng-repeat, I get the same company several times. So I have created a custom filter that filters out the companyName and companyID of each company listed only once.
Everything works in the theory that when I change the value of the company dropdown, the correct subsidiaries are listed. However the value shown in the company box is stuck on the first option listed and will not change. If I remove the custom filter and allow it to list all the repeat names, the box displays correctly.
My first thought is to make a separate HTTP call that would just get companies from my companies table, but I would think I want to limit HTTP calls to as few as possible. Plus it would seem that I should be able to accomplish this.
What concept am I not grasping that prevents this from displaying correctly when I use my filter and what should I do to fix this?
thanks
HTML:
<div class="col-sm-5">
<select ng-model ="parentCompany" name="company">
<option ng-repeat="company in companies | uniqueCompanies:'companyName'" value="{{company.id}}" >{{company.name}}</option>
</select>
</div>
<div class="col-sm-5">
<select name="subsidiary">
<option ng-repeat="subsidary in companies" value="{{subsidary.subID}}" ng-hide="$parent.parentCompany !== subsidary.companyID">{{subsidary.subName}}</option>
</select>
</div>
Controller:
getCompanies();
function getCompanies(){
$http.get("get.php?table=getcompanies").success(function(data) {
$scope.companies = data;
});
}
Filter:
.filter("uniqueCompanies", function() {
return function(data, propertyName) {
if (angular.isArray(data) && angular.isString(propertyName)) {
var results = [];
var keys = {};
for (var i = 0; i < data.length; i++) {
var val = data[i][propertyName];
var val2 = data[i]['companyID'];
if (angular.isUndefined(keys[val])) {
keys[val] = true;
results.push({'name':val, 'id':val2});
}
}
return results;
} else {
return data;
}
};
});
Sample Data :
[{"subID":null,"subName":null,"companyID":"1","companyName":"DWG"},
{"subID":null,"subName":null,"companyID":"2","companyName":"Vista"},
{"subID":"1008","subName":"Data Services","companyID":"3","companyName":"Medcare"},
{"subID":"1009","subName":"Companion","companyID":"3","companyName":"Medcare"},
{"subID":"1010","subName":"GBA","companyID":"3","companyName":"Medcare"},
{"subID":"1011","subName":"PGBA","companyID":"3","companyName":"Medcare"},
{"subID":"1013","subName":"Health Plan","companyID":"3","companyName":"Medcare"},
{"subID":"1014","subName":"PAISC","companyID":"3","companyName":"Medcare"},
{"subID":"1015","subName":"CGS","companyID":"3","companyName":"Medcare"}]
You are creating new objects in your filter with different properties so they will be different every time. You can you track by as mentioned by others. Since filters are executed every digest cycle you may want to set up a $watch and only create a new list of unique companies when your companies change. I actually get the 10 $digest() iterations reached error without doing this.
$scope.$watchCollection('companies', function(newValue) {
$scope.filteredCompanies = $filter('uniqueCompanies')($scope.companies,
'companyName');
});
You could also set a watch on parentCompany and create the list of subsidiaries only when it changes, as well as clear out the value you have for subsidiaryCompany:
$scope.$watch('parentCompany', function(newValue) {
$scope.subsidiaries = [];
for (var i = 0; i < $scope.companies.length; i++) {
var c = $scope.companies[i];
if (c.companyID === newValue) {
$scope.subsidiaries.push(c);
}
}
$scope.subsidiaryCompany = undefined;
});
I may not be fully understanding you're issue here, but it looks like you could filter the data when you get it. Such as ...
function getCompanies(){
$http.get("get.php?table=getcompanies").success(function(data) {
$scope.companies = data.reduce(function (prev, cur) {
// some code for skipping duplicates goes here
}, []);
});
}
Array.reduce may not be the best way to get a new array without duplicates, but that's the general idea, anyway.

AngularJS - Pass $index or the item itself via ng-click?

Say I have
<div ng-repeat="item in scope.allItems">
<div ng-click="doSomething(...)"></div>
</div>
If I want to do something with item upon click, which one is a better option?
Option 1
Use ng-click="doSomething($index) and have:
$scope.doSomething = function($index) {
var myItem = $scope.allItems[$index];
}
Option 2
Use ng-click="doSomething(item) and have:
$scope.doSomething = function(myItem) {
// Do whatever
}
If you are just doing something to the item, pass that in. This way the function doesn't need to know which array the item belongs to:
$scope.addColor = function(car) {
car.color = 'red';
};
If, on the other hand, you need to modify the array I prefer to pass in $index and save having to loop through the array looking for a match:
$scope.deleteCar = function(index, cars) {
cars.splice(index, 1);
};
If you want use filter for scope.allItems, use Option 2 - because using filters change element $index.
If you don't use filter,you can use Option 1.
IMHO Option 2 more easy and useful than Option 1, so i already use Option 2.

How to dynamically change values in drop down using other drop down change using angular?

I have two drop downs. Initially both has values like "First","Second","Third",...
When 1st dropdown has value "First" the second should not offer a value "First" that is second should have other than what is selected from 1st one. I want to do this using angularjs. Values in 2nd dropdown should be changed dynamically.
<select class="form-control" ng-model="Data.firstType">
<option>First</option>
<option>Second</option>
<option>Third</option>
</select>
<select class="form-control" ng-model="Data.SecondType">
<option>First</option>
<option>Second</option>
<option>Third</option>
</select>
Can anyone help on this?
Thanks in advance.
If you're items in the drop downs are static you can do this fiddle
ng-if="Data.FirstType !== First"
If they are dynamic then you can filter the items in the second list based upon the item selected in the first drop down
Here is a fiddle to dynamically filter the second list if the lists are being populated dynamically.
function MyCtrl($scope){
$scope.Data = {};
$scope.items = [{id:1, name:'First'}, {id:2, name:'Second'}, {id:3, name:'Third'}];
$scope.exclude = function(x){
return x !== $scope.Data.firstType;
}
}
Just use a $watch:
$scope.$watch('Data.firstType', function(val) {
//Get index of type in Data.yourArrayData
var idx = $scope.Data.yourArrayData.indexOf(val);
$scope.Data.yourArrayData.splice(idx, 1);
});
This assumes you are binding your second select to yourArrayData using an ng-repeat. I haven't tested this code, but it should be what you need.

ng-hide true if empty filter results?

While filtering a child select object by a parent select value, I get the results of the filter just as expected using the code bellow.
(filter is based on scope variable $scope.le_form.categories.id)
But I just wanna show the child select, if the filter is not empty:
<div ng-show="(labels|filter:labelFilter).length">
<label>Labels in categories</label>
<select ng-options="l.label for l in labels | labelFilter: {cats: le_form.categories.id}" ng-model="le_form.product_labels_uid">
<option value="" disabled>Select</option>
</select>
</div>
So, my filter works, but I don't this ng-show to be true unless the parent id is found on its filter results.
Of course, that condition you see on ng-show works, but it won't hide the div if the filter returns no results. How can I do this?
Basically you can hide through twitter bootstrap by default and ng-show="myValue"
You can write $scope.myValue()=function}{
Do your logic here at controller
get filter item...
}
References:
https://docs.angularjs.org/api/ng/directive/ngShow
Labels in categories
Select
I wrote this $watch with a var to hold the filtered results so I can count them:
$scope.$watch("le_form.categories_uid.id", function(query){
$scope.filteredData = $filter("filter")($scope.labels, query);
});
Then I can:
ng-show="filteredData.length"
But the thing is that filter goes through all the object props, so false results were appearing.
In order to be sure about the filteredData contents, I had to copy the logic of my custom filter inside the $watch:
$scope.$watch("le_form.categories_uid.id", function(query){
var arr = [];
$scope.filteredData = $filter("filter")($scope.labels, query);
for (var i = 0; i < $scope.filteredData.length; i++) {
if ($scope.filteredData[i].categories_uid == query) {
arr.push($scope.filteredData[i]);
}
}
$scope.filteredData = arr;
});
Really have no idea ATM if this is the right way, but it works.

Table filter by predicate

I made a jsfiddle to show what is my problem.
The fisrt part is working in a partial way. See line number 15. I put the predicate in the filter (predicate is l_name) by hand and is working. The table is filtered by Last Name column.
<tr ng-repeat="item in items | filter:{l_name:myInput}">
The second part of the sample is not working when I use the select (model named mySelect2) to choose the predicate where I'm going to filter (see line number 36).
What I'm trying to do is use the select to choose the column by predicate and the input to filter in that column.
<tr ng-repeat="item in items | filter:{mySelect2:myInput2}">
Am I missing something or the binding of the select (mySelect2) must update the filter on the table?
Thanks for the help!
PS: type jo in the input.
Here's a fiddle with some options: http://jsfiddle.net/jgoemat/tgKkD/1/
Option 1 - Search on multiple fields
You can use an object on your model ('search' here) as your filter and separate input boxes for l_name and f_name. This allows you not only to filter on either, but filter on both:
any: <input ng-model="search.$"/><br/>
l_name: <input ng-model="search.l_name"/><br/>
f_name: <input ng-model="search.f_name"/><br/>
<!-- skipping code -->
<tr ng-repeat="item in items|filter:search">
Option 2 - Use a function on your controller
The built-in filter can take a function as an argument that should return true if the object should be included. This function takes the object to be filtered as its only argument and returns true if it should be included. Html:
<tr ng-repeat="item in items|filter:filterFunc">
controller function:
$scope.filterFunc = function(obj) {
// property not specified do we want to filter all instead of skipping filter?
if (!$scope.mySelect)
return obj;
if (obj[$scope.mySelect].toLowerCase().indexOf($scope.myInput.toLowerCase()) >= 0)
return obj;
return false;
};
Option 3 - Create a custom filter
This filter function will take the whole list as an argument and return the filtered list. This does require you to create an angular module and specify it in the ng-app tag like ng-app="MyApp"Html:
<tr ng-repeat="item in items|MyFilter:mySelect:myInput">
Code:
var app = angular.module('MyApp', []);
app.filter('MyFilter', function() {
return function(list, propertyName, value) {
console.log('MyFilter(list, ', propertyName, ', ', value, ')');
// property not specified do we want to filter all instead of skipping filter?
if (!propertyName)
return list;
var newList = [];
var lower = value.toLowerCase();
angular.forEach(list, function(v) {
if (v[propertyName].toLowerCase().indexOf(lower) >= 0)
newList.push(v);
});
return newList;
}
});
Option 4: ng-show
The built-in filter filter expressions don't let you use any expression, but ng-show does so you can just limit visible items like so:
<tr ng-show="item[mySelect].toLowerCase().indexOf(myInput.toLowerCase()) >= 0 || !mySelect" ng-repeat="item in items">
I think option 1 is easy and flexible. If you prefer your drop-down + field UI then I think option 3 is the most useful, and you can re-use it as a dependency in other apps like this:
var app = angular.module("NewApp", ["MyApp"]);
I would just name it something better like 'filterByNamedProperty'. Option 2 is easy but it is tied to your controller. Option 4 is messy and I wouldn't use it.
What about using a custom filter? Users concatenate the property with the criteria (e.g. last:jo). In the filter, split on the colon, and use the first part as the property name and the second part as the criteria.
You may pass scope variables to your filters:
<tr ng-repeat="item in items | filter:myScopeVariable">
This means that you may define your filter object in controller and it will be used by the filter:
$scope.$watch('mySelect2', function(val){
$scope.myScopeVariable = {};
$scope.myScopeVariable[val] = $scope.myInput2;
});
$scope.$watch('myInput2', function(val){
$scope.myScopeVariable = {};
$scope.myScopeVariable[$scope.mySelect2] = $scope.myInput2;
});
Demo Fiddle

Resources