Print only unique values inside ng-repeat - javascript

I have the following code snippet:
<tr ng-repeat="val in myObj">
<td>{{val.Action}}</td>
<td>{{val.Tx}}</td>
<td>{{val.Qty}}</td>
<td>{{val.Price}}</td>
</tr>
I want to print {{val.Action}} only if it is the first unique value in the list. Else I want to print empty space "". Is it possible to do this by creating a filter? Or any other way?

You can solve your problem by checking previous element(items[$index-1]) and, if it's Action field equals current one(items[$index-1].Action == val.Action) you should write empty string, otherwise value. For correctnes of this algoritm, you should implement sorting by this field. As you have filter and orderBy, you can't just write myObj[$index-1], because myObj array and filtered&sorter array(items) not the same, so you should apply assigning: items = (myObj...
angular.module('app', []).controller('MyController', ['$scope', function($scope) {
$scope.myObj = [
{Action:'Action3', Tx:4, Symbol: 'a' },
{Action:'Action1', Tx:1, Symbol: 'c' },
{Action:'Action2', Tx:3, Symbol: 'b' },
{Action:'Action3', Tx:5, Symbol: 'a' },
{Action:'Action1', Tx:2, Symbol: 'c' },
{Action:'Action3', Tx:6, Symbol: 'a' }
];
}])
<script src="//code.angularjs.org/snapshot/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="MyController">
<input type='text' ng-model='someValue' ng-init='someValue="a"'/>
<table>
<tbody>
<tr ng-repeat="val in items = (myObj | filter : { Symbol: someValue } | orderBy: 'Action')">
<td>{{(items[$index-1] && items[$index-1].Action == val.Action) ? '' : val.Action}}</td>
<td>{{val.Tx}}</td>
<td>{{val.Symbol}}</td>
</tr>
</tbody>
</table>
</div>
</body>

Related

Concatenate data in specific column

I have the following table:
<table class="table main-table" ng-if="linesArray && linesArray.length > 0">
<!-- HEADER-->
<thead class="table-head">
<tr>
<th ng-repeat="column in ::columns" width="{{::column.width}}">{{::column.label}}</th>
</tr>
</thead>
<!-- BODY -->
<tbody>
<tr ng-repeat="line in linesArray">
<td ng-repeat="column in ::columns" width="{{::column.width}}" ng-class="{
'center-text' : (!line[column.key] || line[column.key].length === 0)
}">{{line[column.key] !== undefined ? line[column.key] : '--'}}
</td>
</tr>
</tbody>
</table>
Which renders as shown:
WHAT I'M TRYING TO ACHIEVE:
To concatenate the data of two separate fields into one in the first column, which should appear something like this:
As you can see, the column shows the date and time with certain formatting.
The directive which operates the logic of the table:
function historicalSummaryTable($filter) {
return {
restrict: 'EA',
link: link,
templateUrl: 'jfrontalesru/app/components/historicalSummary/historicalSummaryTable.html',
scope: {
linesArray: "=",
columns: "=",
groupField: "#",
groupFieldFilter: "#",
groupFieldFilterFormat: "#"
},
};
function link(scope, element, attrs) {
var _groupField = 'groupField';
var _subgroupField = 'subgroupField';
scope.$watch('linesArray', function(value) {
scope.linesArray.forEach(function(line) {
// Applies the filter for every column if set
scope.columns.forEach(function(column, index) {
// Applies the filter
if (column.filter && column.filter.length > 0) {
line[column.key] = $filter(column.filter)(line[column.key], column.format);
}
});
});
});
}
}
In this directive the input date data is formatted, then it's passed through the controller like this.
vm.historicalColumns = [
{label: $translate('columnDateTime'), key: "timestamp",width:"18%", filter:"date",format:"mediumTime", group:false},
{label: $translate('columnDetails'), key: "detail",width:"50%", group:false},
{label: $translate('columnOrigin'), key: "origin",width:"17%", group:false},
{label: $translate('columnUser'), key: "user",width:"15%", group:false}
];
I'm in the dark here, as I'm not sure how to do this.
Could add a span that uses ng-if to check index
<td ng-repeat="column in ::columns" width="{{::column.width}}" ng-class="{
'center-text' : (!line[column.key] || line[column.key].length === 0)
}">
<span ng-if="$index==0">first column only</span>
{{line[column.key] !== undefined ? line[column.key] : '--'}}
</td>
Or map your data in controller and do the concatenation there

How to ignore property in angular filter

I'm trying to ignore a property called title in my angular filter. I have a dataset like the below example:
const data = [
{
title: 'Title 1'
groups: [
{...},
{...},
{...}
]
},
{
title: 'Title 2'
groups: [
{...},
{...},
{...}
]
},
{
title: 'Title 3'
groups: [
{...},
{...},
{...}
]
}
];
And i'm using the ng-repeat with filter to iterate over the objects, and other loop to iterate over the groups:
<input ng-model="search">
<div ng-repeat="item in data | filter:search">
<h1>{{item.title}}</h1>
<ul>
<li ng-repeat="group in item.group | filter:search">
<span>{{group.something}}</span>
</li>
</ul>
</div>
Is working fine, but now i would like to ignore the title in the search. I did try several things, like: filter:search:item.title (in the first ng-repeat), or remove the first filter:search, but all tries failed. What i'm missing? Do i need a custom search or something like that?
Thank you.
You can specifically enter properties you want to filter and leave out title:
<li ng-repeat="group in item.groups | filter: { something: search }">
The above code will only filter based on the something property.
More answers and explanations here: AngularJS filter only on certain objects
If you type and no filtering the title property, just remove the first filter. This way when you type the li's isnt match will hide, but their h1's will stay the same place.
You should create custom filter, where you can specify which property should be excluded(ignore parameter) from consideration:
angular.module('app', []).controller('MyController', ['$scope', function($scope) {
$scope.data = [
{name:"Tom", title:'London'},
{name:"Max", title:'Moscow'},
{name:"Henry", title:'NY'},
{name:"Paul", title:'NY'},
{name:"Sam", title:'Paris'}
];
}]).filter('myfilter',function(){
return function(input, search, ignore){
if(!search)
return input;
var result = [];
for(var item of input)
for(var prop in item)
if(prop != ignore && item[prop].indexOf(search) != -1)
{
result.push(item) ;
break;
}
return result;
}
});
<script src="//code.angularjs.org/snapshot/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="MyController">
search: <input type='text' ng-model='search' ng-init='search="a"'/>
ignore: <input type='text' ng-model='ignore' ng-init='ignore="title"'/>
<ul>
<li ng-repeat='item in data | myfilter: search: ignore'>
{{item.name}} {{item.title}}
</li>
</ul>
</div>
</body>

How to filter second level ng-repeat in table

I have an array of objects which I want to display as a table with filter. My filter with model name is filtering the deep object family. This works fine but not the way I want it to work...
What I want: Insert string to input, e.g. 'Ma'. Now, I want it to display all items containing a string in family matching 'Ma' - that means I want to keep all the family members displayed as long as one string matches. In my example this would be the filtered result:
Homer Marge, Bart, Lisa, Maggie
Ned Maude, Rod, Todd
Example Code with snippet below:
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function($scope) {
$scope.tableData = [
{id: 1, name: 'Homer', family: ['Marge', 'Bart', 'Lisa', 'Maggie']},
{id: 2, name: 'Carl', family: []},
{id: 3, name: 'Lenny', family: []},
{id: 4, name: 'Clancy', family: ['Sarah', 'Ralph']},
{id: 5, name: 'Ned', family: ['Maude', 'Rod', 'Todd']},
{id: 6, name: 'Moe', family: []}
];
});
table td {
padding: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<table>
<tr>
Filter family members: <input type="text" ng-model="name">
</tr>
<tr ng-repeat="item in tableData">
<td>{{item.name}}</td>
<td>
<span ng-repeat="member in item.family | filter: name">
{{member}}{{$last ? '' : ', '}}
</span>
</td>
</tr>
</table>
</div>
You need to filter the first ng-repeat:
<tr ng-repeat="item in tableData | filter: name && {family: name}">
The key is to provide the name of the property you want to filter on. Here it is the property family.
See fiddle (Type ma in the search box)
Edit: And remove the filter you placed on the inner ng-repeat
You can solve this without a custom filter, by providing a conditional filter on the first ng-repeat. For example:
<tr ng-repeat="item in tableData | filter: (name.length > 0 || '') && {family: name}">
You need the conditional because if you add a filter and remove it, the blank arrays wont be considered (angular quirk).
I have created a working fiddle here
You want to use a custom filter on the top ng-repeat. You can specify a function as the filter: custom filter answer. In this custom filter return all items in tableData that contain a match in the sub array.
Note: You could use lodash to help with this calculation.
Example: plunkr example
<tr ng-repeat="item in tableData | filter:criteriaMatch(name)">
...
$scope.criteriaMatch = function( criteria ) {
return function( item ) {
if(!angular.isDefined(criteria) || criteria === "")
return true;
var results = _.filter(item.family, function(n) {
if(_.contains(n.toUpperCase(), criteria.toUpperCase()))
{
return true;
}
});
if(results.length !== 0)
return true;
else return false;
};
};
Do you try to use the filter in tableData?? Something like this:
<input type="text" ng-model="family">
And
<tr ng-repeat="item in tableData | filter: family">

Angular - Get ng-repeat object

I need to get the current object out of an ng-repeat on ng-click, I can't use $index because I'm using orderBy and therefore it gives me the wrong index relative to the scope object. Idealy I want to be able to click on the object (thumbnail) and have $scope.activePerson gain all that objects values.
My data is structured as follows:
[
{
'name': 'john'
},
{
'name': 'toby'
},
{
'name': 'sarah'
}
]
This is very much simplified, my real objects have 30+ KV pairs and subobjects. There are 10 objects that I'm repeating from (in batches of 4).
My current HTML is:
.item.fourth(ng-repeat="person in people | orderBy:'-name' " ng-show="$index <= 3" nid="{{person.guid}}"
Thanks
It's just person in ng-repeat="person in people";
I'm not sure what kind of markdown you're using, you definitely don't have html there, but you want something like:
<div
ng-repeat="person in people | orderBy:'-name' "
ng-show="$index <= 3"
nid="{{person.guid}}"
ng-click="activePerson = person">
</div>
Note that ng-repeat creates a child scope, so you'll want to have activePerson already set in the parent scope.
You can just use orderBy and copy the current object from ng-repeat, see this plunkr. Relevant code:
Controller
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.stuff = [
{
'name': 'john'
},
{
'name': 'toby'
},
{
'name': 'sarah'
}
];
$scope.setActivePerson = function (obj) {
$scope.activePerson = obj;
};
});
View
<body ng-controller="MainCtrl">
<div ng-repeat="thing in stuff | orderBy: 'name'">
<input type="radio" ng-click="setActivePerson(thing)" />
{{ thing.name }}
</div>
<br />
<div ng-model="activePerson">Active: {{ activePerson.name }}</div>
</body>

apply formatting filter dynamically in a ng-repeat

My goal is to apply a formatting filter that is set as a property of the looped object.
Taking this array of objects:
[
{
"value": "test value with null formatter",
"formatter": null,
},
{
"value": "uppercase text",
"formatter": "uppercase",
},
{
"value": "2014-01-01",
"formatter": "date",
}
]
The template code i'm trying to write is this:
<div ng-repeat="row in list">
{{ row.value | row.formatter }}
</div>
And i'm expecting to see this result:
test value with null formatter
UPPERCASE TEXT
Jan 1, 2014
But maybe obviusly this code throws an error:
Unknown provider: row.formatterFilterProvider <- row.formatterFilter
I can't immagine how to parse the "formatter" parameter inside the {{ }}; can anyone help me?
See the plunkr http://plnkr.co/edit/YnCR123dRQRqm3owQLcs?p=preview
The | is an angular construct that finds a defined filter with that name and applies it to the value on the left. What I think you need to do is create a filter that takes a filter name as an argument, then calls the appropriate filter (fiddle) (adapted from M59's code):
HTML:
<div ng-repeat="row in list">
{{ row.value | picker:row.formatter }}
</div>
Javascript:
app.filter('picker', function($filter) {
return function(value, filterName) {
return $filter(filterName)(value);
};
});
Thanks to #karlgold's comment, here's a version that supports arguments. The first example uses the add filter directly to add numbers to an existing number and the second uses the useFilter filter to select the add filter by string and pass arguments to it (fiddle):
HTML:
<p>2 + 3 + 5 = {{ 2 | add:3:5 }}</p>
<p>7 + 9 + 11 = {{ 7 | useFilter:'add':9:11 }}</p>
Javascript:
app.filter('useFilter', function($filter) {
return function() {
var filterName = [].splice.call(arguments, 1, 1)[0];
return $filter(filterName).apply(null, arguments);
};
});
I like the concept behind these answers, but don't think they provide the most flexible possible solution.
What I really wanted to do and I'm sure some readers will feel the same, is to be able to dynamically pass a filter expression, which would then evaluate and return the appropriate result.
So a single custom filter would be able to process all of the following:
{{ammount | picker:'currency:"$":0'}}
{{date | picker:'date:"yyyy-MM-dd HH:mm:ss Z"'}}
{{name | picker:'salutation:"Hello"'}} //Apply another custom filter
I came up with the following piece of code, which utilizes the $interpolate service into my custom filter. See the jsfiddle:
Javascript
myApp.filter('picker', function($interpolate ){
return function(item,name){
var result = $interpolate('{{value | ' + arguments[1] + '}}');
return result({value:arguments[0]});
};
});
One way to make it work is to use a function for the binding and do the filtering within that function. This may not be the best approach: Live demo (click).
<div ng-repeat="row in list">
{{ foo(row.value, row.filter) }}
</div>
JavaScript:
$scope.list = [
{"value": "uppercase text", "filter": "uppercase"}
];
$scope.foo = function(value, filter) {
return $filter(filter)(value);
};
I had a slightly different need and so modified the above answer a bit (the $interpolate solution hits the same goal but is still limited):
angular.module("myApp").filter("meta", function($filter)
{
return function()
{
var filterName = [].splice.call(arguments, 1, 1)[0] || "filter";
var filter = filterName.split(":");
if (filter.length > 1)
{
filterName = filter[0];
for (var i = 1, k = filter.length; i < k; i++)
{
[].push.call(arguments, filter[i]);
}
}
return $filter(filterName).apply(null, arguments);
};
});
Usage:
<td ng-repeat="column in columns">{{ column.fakeData | meta:column.filter }}</td>
Data:
{
label:"Column head",
description:"The label used for a column",
filter:"percentage:2:true",
fakeData:-4.769796600014472
}
(percentage is a custom filter that builds off number)
Credit in this post to Jason Goemaat.
Here is how I used it.
$scope.table.columns = [{ name: "June 1 2015", filter: "date" },
{ name: "Name", filter: null },
] etc...
<td class="table-row" ng-repeat="column in table.columns">
{{ column.name | applyFilter:column.filter }}
</td>
app.filter('applyFilter', [ '$filter', function( $filter ) {
return function ( value, filterName ) {
if( !filterName ){ return value; } // In case no filter, as in NULL.
return $filter( filterName )( value );
};
}]);
I improved #Jason Goemaat's answer a bit by adding a check if the filter exists, and if not return the first argument by default:
.filter('useFilter', function ($filter, $injector) {
return function () {
var filterName = [].splice.call(arguments, 1, 1)[0];
return $injector.has(filterName + 'Filter') ? $filter(filterName).apply(null, arguments) : arguments[0];
};
});
The newer version of ng-table allows for dynamic table creation (ng-dynamic-table) based on a column configuration. Formatting a date field is as easy as adding the format to your field value in your columns array.
Given
{
"name": "Test code",
"dateInfo": {
"createDate": 1453480399313
"updateDate": 1453480399313
}
}
columns = [
{field: 'object.name', title: 'Name', sortable: 'name', filter: {name: 'text'}, show: true},
{field: "object.dateInfo.createDate | date :'MMM dd yyyy - HH:mm:ss a'", title: 'Create Date', sortable: 'object.dateInfo.createDate', show: true}
]
<table ng-table-dynamic="controller.ngTableObject with controller.columns" show-filter="true" class="table table-condensed table-bordered table-striped">
<tr ng-repeat="row in $data">
<td ng-repeat="column in $columns">{{ $eval(column.field, { object: row }) }}</td>
</tr>
</table>
I ended up doing something a bit more crude, but less involving:
HTML:
Use the ternary operator to check if there is a filter defined for the row:
ng-bind="::data {{row.filter ? '|' + row.filter : ''}}"
JS:
In the data array in Javascript add the filter:
, {
data: 10,
rowName: "Price",
months: [],
tooltip: "Price in DKK",
filter: "currency:undefined:0"
}, {
This is what I use (Angular Version 1.3.0-beta.8 accidental-haiku).
This filter allows you to use filters with or without filter options.
applyFilter will check if the filter exists in Angular, if the filter does not exist, then an error message with the filter name will be in the browser console like so...
The following filter does not exist: greenBananas
When using ng-repeat, some of the values will be undefined. applyFilter will handle these issues with a soft fail.
app.filter( 'applyFilter', ['$filter', '$injector', function($filter, $injector){
var filterError = "The following filter does not exist: ";
return function(value, filterName, options){
if(noFilterProvided(filterName)){ return value; }
if(filterDoesNotExistInAngular(filterName)){ console.error(filterError + "\"" + filterName + "\""); return value; }
return $filter(filterName)(value, applyOptions(options));
};
function noFilterProvided(filterName){
return !filterName || typeof filterName !== "string" || !filterName.trim();
}
function filterDoesNotExistInAngular(filterName){
return !$injector.has(filterName + "Filter");
}
function applyOptions(options){
if(!options){ return undefined; }
return options;
}
}]);
Then you use what ever filter you want, which may or may not have options.
// Where, item => { name: "Jello", filter: {name: "capitalize", options: null }};
<div ng-repeat="item in items">
{{ item.name | applyFilter:item.filter.name:item.filter.options }}
</div>
Or you could use with separate data structures when building a table.
// Where row => { color: "blue" };
// column => { name: "color", filter: { name: "capitalize", options: "whatever filter accepts"}};
<tr ng-repeat="row in rows">
<td ng-repeat="column in columns">
{{ row[column.name] | applyFilter:column.filter.name:column.filter.options }}
</td>
</tr>
If you find that you require to pass in more specific values you can add more arguments like this...
// In applyFilter, replace this line
return function(value, filterName, options){
// with this line
return function(value, filterName, options, newData){
// and also replace this line
return $filter(filterName)(value, applyOptions(options));
// with this line
return $filter(filterName)(value, applyOptions(options), newData);
Then in your HTML perhaps your filter also requires a key from the row object
// Where row => { color: "blue", addThisToo: "My Favorite Color" };
// column => { name: "color", filter: { name: "capitalize", options: "whatever filter accepts"}};
<tr ng-repeat="row in rows">
<td ng-repeat="column in columns">
{{ row[column.name] | applyFilter:column.filter.name:column.filter.options:row.addThisToo }}
</td>
</tr>

Categories

Resources