How to bind two ng-models values together - angularjs

I have a JSON file displayed as a table, I want to search inside this table using two steps:
Selecting an option from a select menu to choose in which column you want to search.
an input, for typing the keyword you want to search for.
So how can I concatenate the value of the "selected option form select tag" with the value of the "input"?
For example, the User selected the option "Names" from the select menu then he entered "John" inside the input
Here is my code:
https://jsfiddle.net/p1nkfpez/7/
var app = angular.module("myApp", []);
app.controller("myCtrl", function($scope, $http) {
$scope.selection = "nm";
$http.get("https://api.myjson.com/bins/i9h1v")
.then(function(response) {
$scope.myRows = response.data;
$scope.rowsStatus = response.status;
$scope.rowsStatusText = response.statusText;
});
});
I want the Angular filter to be like:
filter:keyword.selection

You can create a function to create the filter object dynamicly:
$scope.getFilter = function() {
return {
[$scope.selection]: $scope.keyword
}
}
And use it like this:
<tr data-ng-repeat="row in myRows | filter:getFilter()">
...
</tr>

You can create a custom filter in which you can access your properties by name.
Custom filter
$scope.myCustomFilter = function(row){
if($scope.keyword == undefined || $scope.keyword.length == 0)
return true;
if(row[$scope.selection].toLowerCase().indexOf($scope.keyword) >= 0){
return true;
}
else
return false;
}
And add filter to your ng-repeat
<tbody>
<tr data-ng-repeat="row in myRows | filter: myCustomFilter">
<td>{{$index + 1}}</td>
<td>{{row.nm}}</td>
<td>{{row.cty}}</td>
<td>{{row.hse}}</td>
<td>{{row.yrs}}</td>
</tr>
</tbody>
Updated Fiddle

Related

Angular JS multiple filter query

I'm a newcomer to Angular/JS so please be kind ...
I'm having a problem (probably with myself trying to wrap my brain around Angular) with some code where I want to use two different filters (I think) on a dataset with ng-repeat. Firstly here is a screen shot of the html form items and data output I am working with;
Here is the basic (stripped down) HTML code for the setup above;
<div ng-controller="customersCrtl">
Records Per Page:
<select ng-model="entryLimit" class="form-control">
<option>50</option>
<option>100</option>
<option>250</option>
<option>500</option>
<option>1000</option>
</select>
Record Type:
<select ng-model="entryType" class="form-control">
<option>All</option>
<option>Baptism</option>
<option>Marriage</option>
<option>Burial</option>
</select>
Filter Text:
<input type="text" ng-model="search" ng-change="filter()" placeholder="Filter" class="form-control" />
Filtered {{ filtered.length }} of {{ totalItems }} Total Records
<div ng-show="filteredItems > 0">
<table>
<thead>
<th>Event Date <a ng-click="sort_by('eventDate');">sort</a></th>
<th>Event Type <a ng-click="sort_by('type');">sort</a></th>
<th>Indivdual(s) <a ng-click="sort_by('name');">sort</a></th>
<th>Location <a ng-click="sort_by('location');">sort</a></th>
<th></th>
</thead>
<tbody>
<tr ng-repeat="data in filtered = (list | filter:search | orderBy : predicate :reverse) | startFrom:(currentPage-1)*entryLimit | limitTo:entryLimit">
<td>{{data.eventDate | date:'mediumDate'}}</td>
<td>{{data.type}}</td>
<td>{{data.name}}</td>
<td>{{data.location}}</td>
<td>View Record</td>
</tr>
</tbody>
</table>
</div>
... and here is the associated Angular JS code I currently have;
var app = angular.module('myApp', ['ui.bootstrap']);
app.filter('startFrom', function() {
return function(input, start) {
if(input) {
start = +start; //parse to int
return input.slice(start);
}
return [];
}
});
app.controller('customersCrtl', function ($scope, $http, $timeout) {
$http.get('ajax/getMarkers.php').success(function(data){
$scope.list = data;
$scope.currentPage = 1; //current page
$scope.entryLimit = 50; //max no of items to display in a page
$scope.entryType = 'All'; //Record type to display in a page
$scope.filteredItems = $scope.list.length; //Initially for no filter
$scope.totalItems = $scope.list.length;
});
$scope.setPage = function(pageNo) {
$scope.currentPage = pageNo;
};
$scope.filter = function() {
$timeout(function() {
$scope.filteredItems = $scope.filtered.length;
}, 10);
};
$scope.sort_by = function(predicate) {
$scope.predicate = predicate;
$scope.reverse = !$scope.reverse;
};
});
getMarkers.php in the above Angular code snippet returns JSON results formatted like so;
{"id":"646","eventDate":"1576-05-13","name":"John Phillip","type":"Baptism","churchName":"St. Marys","locationid":"563","location":"Swainswick, Somerset, UK","lat":"51.414211","lng":"-2.351418"},
{"id":"647","eventDate":"1577-07-01","name":"Jane Volantyne","type":"Baptism","churchName":"St. Mary the Virgin","locationid":"564","location":"Horsham, Sussex, UK","lat":"51.059750","lng":"-0.330879"},
{"id":"132","eventDate":"1634-05-09","name":"Katherine Stanley","type":"Burial","churchName":"St. James","locationid":"567","location":"Titsey, Surrey, UK","lat":"51.276505","lng":"0.018909"}
... etc. etc.
This currently works in so-far-as if a user types something in the "Filter Text" box, the table updates to only show entries that match the user text.
This is fine, but what I also want to be able to do is use the drop down "Record Type" box (with the defined entries All, Baptism, Burial, Marriage) to only show/filter the records with that particular "Event Type" in the table.
Is this even possible? Can you apply two concurrent filters to an ng-repeat in Angular, because I've tried and failed several times and am on the verge of giving up!!!
Am I asking something stupidly difficult or stupidly easy?
Yes, its very possible to filter the table based on multiple conditions, You could add a filter function to the table as:
<tr ng-repeat="data in filtered = (list | filter:filterEvents | orderBy : predicate :reverse) | startFrom:(currentPage-1)*entryLimit | limitTo:entryLimit">
<td>{{data.eventDate | date:'mediumDate'}}</td>
<td>{{data.type}}</td>
<td>{{data.name}}</td>
<td>{{data.location}}</td>
<td>View Record</td>
</tr>
Basically, $scope.filterEvents is called every time the table is populated and you could filter out the required items using this in the controller:
$scope.filterEvents = function(item){
//Here item is the array element from the table
which is automatically passed in the filter function.
return item.type.toLowerCase().indexOf($scope.entryType.toLowerCase()) != -1 ||
item.name.toLowerCase().indexOf($scope.search.toLowerCase()) != -1
}
Where , $scope.entryType is the option selected in the dropdown and $scope.search is the keyword for searching.
Ps: You would need to remove the ng-change=filter() from search textbox and handle the return if $scope.entryType = "All"

How can I easily filter on more than one property?

Using Angular v1.5 I've got a simple one property filter working but I would like to filter on two properties. Searching came up with some custom filters but that seemed like a bunch of code to just look at an extra property.
In my below example I would like the filter to work on both FirstName and LastName.
Filter Name: <input type="text" ng-model="memberFilter.FirstName" />
<p />
<div class="table-responsive">
<table class="table table-bordered table-striped table-hover">
<tr class="info">
<th ng-click="doSort('FirstName')">First Name</th>
<th ng-click="doSort('LastName')">Last Name</th>
<th ng-click="doSort('EmailPrimary')">Email</th>
<th ng-click="doSort('PhonePrimary')">Phone</th>
<th> </th>
</tr>
<tr ng-repeat="member in members | filter:memberFilter | orderBy:sortBy:reverse">
<td>{{ member.FirstName }}</td>
<td>{{ member.LastName }}</td>
<td>{{ member.EmailPrimary }}</td>
<td class="text-center">{{ member.PhonePrimary | tel }}</td>
<td class="text-center">View</td>
</tr>
</table>
</div>
I tried adding an array to the ng-model but that didn't work:
ng-model="[memberFilter.FirstName,memberFilter.LastName]"
Updated Question
I'm new with Angular in general and just wrote my first custom filter the other day to display formatted telephone numbers. You can see my filter in the original code but I changed the name from "tel" to "phone". It's one I found on SO and I was able to get it to work. So I assumed I could just add the search filter to this file and not create a new js file for each filter. However, when I added it my Angular stopped working. I thought I added it correct but I might have a semi-colon in the wrong spot or maybe it's not possible. I still haven't figured out a good way to debug in Angular.
Here is a file I created for my filters called filter.js.
angular.module('appFilters', [])
.filter('phone', function () {
return function (phone) {
if (!phone) { return ''; }
var value = phone.toString().trim().replace(/^\+/, '');
if (value.match(/[^0-9]/)) {
return phone;
}
var country, city, number;
switch (value.length) {
case 10: // +1PPP####### -> C (PPP) ###-####
country = 1;
city = value.slice(0, 3);
number = value.slice(3);
break;
case 11: // +CPPP####### -> CCC (PP) ###-####
country = value[0];
city = value.slice(1, 4);
number = value.slice(4);
break;
case 12: // +CCCPP####### -> CCC (PP) ###-####
country = value.slice(0, 3);
city = value.slice(3, 5);
number = value.slice(5);
break;
default:
return phone;
}
if (country == 1) {
country = "";
}
number = number.slice(0, 3) + '-' + number.slice(3);
return (country + " (" + city + ") " + number).trim();
};
})
.filter('customMemberFilter', function () {
return function (memberList, query) {
// make sure a query value was passed
if (query) {
query = query.toLowerCase();
var out = [];
angular.forEach(memberList, function (member) {
if ((member.FirstName.toLowerCase().includes(query)) ||
(member.LastName.toLowerCase().includes(query))) {
out.push(member);
}
});
return out;
} else {
return memberList;
}
}
});
and this is my app.js where the appFilters are added
(function () {
var app = angular.module('troopApp', ['ngRoute','appFilters']); //('moduleName', [array of injected modules])
app.config(function($routeProvider) {
$routeProvider
.when('/', {
controller: 'MembersController',
templateUrl: 'app/views/members.html'
})
.when('/memberDetail/:memberId', {
controller: 'MemberDetailController',
templateUrl: 'app/views/memberDetail.html'
})
.otherwise({ redirectTo: '/'});
});
}());
This is what I changed my HTML code to:
Filter Name: <input type="text" ng-model="memberFilter" />
and
<tr ng-repeat="member in members | customMemberFilter:memberFilter | orderBy:sortBy:reverse">
The solution is very simple.
Change this line:
Filter Name: <input type="text" ng-model="memberFilter.FirstName" />
to this:
Filter Name: <input type="text" ng-model="memberFilter" />
By default Angular will do deep property filtering. Since you were creating an object with a FirstName property to use as your filter you were effectively limiting the filter to that property on your target object(s). By making the filter more generic Angular will attempt to match on any property of the target object(s).
Update: This will, of course, match on all properties of your member object which may be undesirable. If this is the case then, as you suspected, you'll have to write a custom filter. Here's one way you could write it:
.filter('customMemberFilter', function() {
return function(memberList, query) {
// make sure a query value was passed
if (query) {
query = query.toLowerCase();
var out = [];
angular.forEach(memberList, function(member) {
if ((member.FirstName.toLowerCase().includes(query)) ||
(member.LastName.toLowerCase().includes(query))) {
out.push(member);
}
});
return out;
} else {
return memberList;
}
}
})
And then you would use it:
<tr ng-repeat="member in members | customMemberFilter:memberFilter | orderBy:sortBy:reverse">

Angular binding updates when service provides unfiltered array but doesn't when using Underscore

I have an angular service which contains two function to return an array of objects. The first returns the entire array, and the second a filtered array based on an ID. When I use the first function returning all the objects, data-binding works automatically and my front end updates as soon as I push a new object to the array. However when I use the function that returns a filtered list using underscore, my frontend doesn't update automatically.
I've done some research and the only thing similar to this that I have seen is in relation to async requests and using promises, but I'm not sure if this is appropriate in this case as I'm only using in service objects currently.
Service
angular.module('RankingsApp')
.service('results', function() {
var uid = 2;
var results = [
{
"id":1,
"fencer":1,
"competition":1,
"placing":1,
"points":50
}
];
this.getResults = function()
{
return results;
}
this.getResultsForCompetition = function (_id)
{
var resultsForCompetition = _.filter(results, function(x){ return x.competition == _id});
return resultsForCompetition;
};
this.insertResult = function (result) {
result.id = uid++;
results.push(result);
};
});
Controller
angular.module('RankingsApp')
.controller('CompetitionCtrl', function ($scope, competitions,fencers,results, $routeParams) {
$scope.getResults = function()
{
return results.getResultsForCompetition($routeParams.competitionID);
}
$scope.competition = competitions.getCompetition($routeParams.competitionID);
$scope.fencers = fencers.getFencers();
$scope.compResults = results.getResultsForCompetition($routeParams.competitionID);
function getNextPlacing()
{
return $scope.compResults.length + 1;
}
$scope.getFencerFromResult = function(result)
{
return fencers.getFencer(result.fencer);
}
$scope.getCompFromResult = function(result)
{
return competitions.getCompetition(result.competition);
}
$scope.addNewResult = function(fencer)
{
var result = { "fencer": fencer.id, "competition":$routeParams.competitionID, "placing": getNextPlacing(), "points":50 };
results.insertResult(result);
$scope.selectedFencer = null;
}
});
View
<table style="width: 100%">
<thead>
<tr>
<th>Placeing</th>
<th>Fencer</th>
<th>Comp</th>
<th>Points</th>
<th>Edit</th>
</tr>
</thead>
<tbody>
<tr ng-repeat='result in compResults'>
<td>{{result.placing}}</td>
<td>{{getFencerFromResult(result).firstname}} {{getFencerFromResult(result).lastname}}</td>
<td>{{getCompFromResult(result).shortName}}</td>
<td>{{result.points}}</td>
<td><a>Edit</a></td>
</tr>
</tbody>
</table>
It's because your method (with _.filter()) returns another array than what your view in the frontend was bind to (as binding is done by reference in case of an Array or an Object).
To solve this, you may place filtering logic in views and use ng-repeat.
If it's not an option, you should directly modify the results variable in the service by using pop()/push() methods.
Update:
<tr ng-repeat='result in compResults'>
should be
<tr ng-repeat='result in compResults | filter{ competition: _id }'>
where
$scope.compResults = results.getResults();
and
$scope._id = $routeParams.competitionID;
found here
Using the advice posted by #Mironor I was able to come up with the following solution which solves my issue. By changing the ng-repeat to call the function directly the list updates itself when I push a new value to the service.
View
<tr ng-repeat='result in getResultsForCompetition(competitionID)'>

Custom order using orderBy in ng-repeat

I have objects like this:
students = {name: 'Aa_Student', class: 'A_Class'},
{name: 'Ab_Student', class: 'A_Class'},
{name: 'Ac_Student', class: 'B_Class'},
{name: 'Ba_Student', class: 'B_Class'},
{name: 'Bb_Student', class: 'C_Class'},
{name: 'Bc_Student', class: 'C_Class'}
Let's say the students object is shuffled. I use ng-repeat to show the data. I want to sort the objects in the custom order.
For example, I want to show the data like this:
Name Class
-----------------------------
Ac_Student B_Class
Ba_Student B_Class
Aa_Student A_Class
Ab_Student A_Class
Bb_Student C_Class
Bc_Student C_Class
So basically, I want to order by student's class, but it B_Class comes first, then A_Class, then C_Class. Also, I want to order by students name in alphabetic order. How can I do this?
HTML:
<table>
<tr ng-repeat="student in students | orderBy:customOrder">
...
</tr>
</table>
Controller:
$scope.customOrder = function(student) {
$scope.students = $filter('orderBy')(student, function() {
});
};
Hi you can create custom sort filter please see here http://jsbin.com/lizesuli/1/edit
html:
<p ng-repeat="s in students |customSorter:'class'">{{s.name}} - {{s.class}} </p>
</div>
angularjs filter:
app.filter('customSorter', function() {
function CustomOrder(item) {
switch(item) {
case 'A_Class':
return 2;
case 'B_Class':
return 1;
case 'C_Class':
return 3;
}
}
return function(items, field) {
var filtered = [];
angular.forEach(items, function(item) {
filtered.push(item);
});
filtered.sort(function (a, b) {
return (CustomOrder(a.class) > CustomOrder(b.class) ? 1 : -1);
});
return filtered;
};
});
Know this is old but may come in handy for others...
You could also create a simple custom sort function. "Not quite a filter":
$scope.customOrder = function (item) {
switch (item) {
case 'A_Class':
return 2;
case 'B_Class':
return 1;
case 'C_Class':
return 3;
}
};
And then use like you wanted to:
<table>
<tr ng-repeat="student in students | orderBy:customOrder">
...
</tr>
to set the orderBy as a property of the objects just quote that property name within the markup:
ng-repeat="student in students |orderBy:'name' | orderBy:'class'"
DEMO

Angularjs: two ng-repeats not showing the correct information in table column

I'm trying to create a dynamic table that could hold search results with different amount of columns.
I created a table that should have a row for every entry and a column for every datafield both populated with ng-repeat -functions, but for some reason it doesn't show any information in the columns at all, although it does create correct amount of them.
If I try to show e in {{}} it shows the correct key that exists. If I try with i in {{}} it shows the following in each column (the information is same for all columns, but different for every row):
{"etunimi":"firstname","sukunimi":"lastname","optunnus":"010101010101011001"}
Here is the html:
<table id="raporttiTulos" class="resultTable">
<tr ng-repeat="i in raportointiLista">
<td ng-repeat=" e in raportointiAvaimet">{{i.e}}</td>
</tr>
</table>
Here is the function responsible for the incoming data:
$scope.haeMaksut = function(){
$scope.raportointiAvaimet = {};
$http.post('/maksuhaku')
.then(function(res){
x = 0;
$scope.raportointiLista = res.data.message;
for(i in $scope.raportointiLista[0]){
$scope.raportointiAvaimet[x] = i;
x+=1
}
console.log($scope.raportointiAvaimet);
$scope.maksamattomat = $scope.raportointiLista.length;
$scope.lataus = true;
}, function(error){
console.log(error);
});
}
This is how the key list looks like:
Object [ "etunimi", "sukunimi", "optunnus" ]
Here are some rows from the data list:
[…]
[0…99]
0: Object { etunimi: "firstname", sukunimi: "lastname", optunnus: "101010101010101010", … }
instead of doing that you can directly access object keys.
HTML
<tr ng-repeat="i in raportointiLista">
<td ng-repeat="key in raportointiAvaimet">{{i[key]}}</td>
</tr>
Controller
$scope.haeMaksut = function () {
$scope.raportointiAvaimet = {};
$http.post('/maksuhaku')
.then(function (res) {
$scope.raportointiLista = res.data.message;
$scope.raportointiAvaimet = Object.keys($scope.raportointiLista[0]);
}, function (error) {
console.log(error);
});
}

Resources