Angular 2 filtering - javascript

I've tried to filter in Angular 2 app in version alpha 22. I've tried many ways how to do it but nothing works...
<table class="tabulka">
<tr>
<th>ID</th><th>Typ</th><th>Priorita</th><th>Aplikace</th><th>Souhrn</th><th>Hlásil</th><th>Stav</th><th>Termín</th><th>Akce</th>
</tr>
<tr *for="#x of datas">
<td>{{x.ID}}</td>
<td>{{x.Type}}</td>
<td *if="x.Priority == 1" ><img src="./img/red.png"></td>
<td *if="x.Priority == 0"></td>
<td>{{x.Aplication}}</td>
<td>{{x.Summary}}</td>
<td>{{x.Person}}</td>
<td>{{x.State}}</td>
<td>{{x.Date}}</td>
<td class="edit" id="{{x.ID}}">Upravit</td>
</tr>
</table>
Please help! How do you do filtering in angular 2 using typescript?
In angular 1.4.x it works this way:
<table class="tabulka">
<tr ng-repeat="x in datas| filter:searchText|filter:{Aplication:search}| filter:{Person:podle}">
<td>{{x.ID}}</td>
<td>{{x.Type}}</td>
<td>{{x.Priority}}</td>
<td>{{x.Aplication}}</td>
<td>{{x.Summary}}</td>
<td>{{x.Person}}</td>
<td>{{x.State}}</td>
<td>{{x.Date}}</td>
<td class="edit" id="{{x.ID}}">Upravit</td>
</tr>
</table>

In angular 2.0.0-beta.0, you'll need to implement a pipe that transform the array depending on your application needs,
#Pipe({
name: 'search'
})
export class SearchTextPipe implements PipeTransform {
transform(value: any[] , args: any[]) {
const searchText = args[0];
const field = args[1];
if (!searchText) {
return value;
}
return value.filter ((item) => {
if (field) {
return item[field].includes(searchText);
}
return _(item)
.values()
.includes( searchText );
})
}
}
Then you can use it in other components:
#Component({
...
pipes: [SearchTextPipe]
})
and in the template:
*ngFor="#item of list | search:searchInput:field"

Go to the console and type
ng generate pipe filter
Then go edit the newly created file (src/app/filter.pipe.ts) and replace
transform(value: any, args?: any): any {
return null;
}
by
transform(value: any, args?: any): any {
if (!value || !args) return value;
if (typeof args == "string"){
return value.filter(item => item.toLowerCase().indexOf(args.toLowerCase()) !== -1);
} else {
let key = Object.keys(args)[0];
return value.filter(item => item[key].toLowerCase().indexOf(args[key].toLowerCase()) !== -1);
}
}
Usage
Now, you can use your filter as follow
// app.component.ts
list = ["Hello", "Hi and hello", "Bonjour"];
list_of_objects = [
{ id: 0, content: "Hello" },
{ id: 1, content: "Hi and hello" },
{ id: 2, content: "Bonjour" }
];
// app.component.html
<p>Simple array</p>
<ul>
<li *ngFor="let item of list | filter:'hello' ">{{ item }}</li>
</ul>
<p>Array of JSONs</p>
<ul>
<li *ngFor="let item of list_of_objects | filter:{ content:'hello' } ">{{ item.title }}</li>
</ul>
And all of that will display :
Simple array
Hello
Hi and hello
Array of JSONs
Hello
Hi and hello

You have to write *ng-for="#x of datas" and *ng-if="x.Priority == 1""
<table class="tabulka"> <tr>
<th>ID</th><th>Typ</th><th>Priorita</th><th>Aplikace</th><th>Souhrn</th><th>Hlásil</th><th>Stav</th><th>Termín</th><th>Akce</th> </tr> <tr *ng-for="#x of datas">
<td>{{x.ID}}</td>
<td>{{x.Type}}</td>
<td *ng-if="x.Priority == 1" ><img src="./img/red.png"></td>
<td *ng-if="x.Priority == 0"></td>
<td>{{x.Aplication}}</td>
<td>{{x.Summary}}</td>
<td>{{x.Person}}</td>
<td>{{x.State}}</td>
<td>{{x.Date}}</td>
<td class="edit" id="{{x.ID}}">Upravit</td> </tr>

I played around with the code below. i was looking for the same search function. But for now this fits my needs. Still want to find a way the make the pipe dynamic. Maybe you find the solutions for this one.
import {Pipe} from 'angular2/core';
#Pipe({
name: 'filterByDone',
pure: false,
})
export class SearchPipe {
transform (value, [queryString]) {
if (value==null) {
return null;
}
return value.filter((todo)=> todo.done !== '1')
}
}

Basicly, add a function to trigger search box textvalue changed. Inside the function, create a for loop to add matched data.
TextChanged(searchText){
var filteredList = [];
For(let item of this.oldList){
If(item.contains(seaechText))
FilteredList.push(item);
}
This.oldList = filteredList;
}
}

Related

Move v-for logic to computed property

With my code is nothing wrong (i hope), although I feel it could be better written. I tried move logic from to computed property but unsuccessfully, i think table structure is not correct, but I'm out of ideas. Anyone can help ?
Unfortunately "tabl" comes from the server and i cant changes this variable
<template>
<movable-div>
<template #header>
<div class="header">
<h3>{{ name }}</h3>
<div #mousedown.stop="dragMouseDown">
<input type="text" v-model="search" placeholder="Search..." />
</div>
<div class="button-group">
<svg width="1.2em" height="1.2em" viewBox="0 0 10240 10240" #click="toggleTable()" :class="[showTable ? 'go' : 'back']">
<path some long svg code... />
</svg>
<p #click="showTableAttributes()">X</p>
</div>
</div>
<table v-if="showTable">
<tr>
<th v-for="head in tableHead" :key="head.Name">
{{ head.Name }}
</th>
</tr>
<tr
v-for="row in filteredRow"
:key="row.Key"
class="data"
#click="zoomToFeatureExtent(row)"
>
<td v-for="item in tableHead" :key="item.Name">
<p v-html="row.filter((obj) => obj.Key === item.Name)
.map((item) => item.Value)
.join()
.replace(search, `<span style='color:#1D7CA7'><b>${search}</b></span>`)">
</p>
</td>
</tr>
</table>
</template>
</movable-div>
</template>
<script>
export default {
props: ['olMap', 'LayerStyleName', 'name'],
data() {
return {
tableHead: null,
rows: null,
table: {
ColumnList: [{name: "ex1"},{name: "ex2"}],
Name: "Example",
RowList: [{Original:[{Key: "ex1", Value: "exampleValue"}]},
{Original:[{Key: "ex2", Value: "exampleValue"}]}]
},
showTable: true,
layer: null,
filteredRow: [],
search: null,
};
},
mounted() {
this.rows = this.table.RowList;
this.tableHead = this.table.ColumnList.filter((item) => item.Name !== 'geometry');
this.search = '';
},
inject: ['showTableAttributes'],
methods: {
toggleTable() {
this.showTable = !this.showTable;
},
zoomToFeatureExtent(value) {
let extent = value
.filter((item) => item.Key === 'geometry')
.map((item) => item.Value);
let view = this.olMap.getView();
view.fit(extent[0], this.olMap.getSize());
let res = view.getResolution();
if (res < 0.5) {
view.setResolution(0.9);
}
},
},
watch: {
search: function (val) {
this.filteredRow = [];
for (let row of this.rows) {
if (row.Original.map((obj) => obj.Value.toString().includes(val))
.filter((i) => (i === true ? i : null))
.join()) {
this.filteredRow.push(row.Original);
} else null;
}
},
},
};
</script>
It's good practice (IMHO) to have the template void of any complex logic. It makes the code more maintainable since you don't have functionality split between your template and your script. It also allows for better performance if you can offload methods to cached variables which prevents static parts of code from re-calculating needlessly.
The following is a good example of improvement potential
<tr
v-for="row in filteredRow"
:key="row.Key"
class="data"
#click="zoomToFeatureExtent(row)"
>
<td v-for="item in tableHead" :key="item.Name">
<p
v-html="row.filter((obj) => obj.Key === item.Name)
.map((item) => item.Value)
.join()
.replace(search, `<span style='color:#1D7CA7'><b>${search}</b></span>`)"
></p>
</td>
</tr>
I find reading this in the template is harder than in the script block (but YMMV), but performance wise you're doing extra loops. This script here does 3 loops: rows(filteredRow), columns(tableHead), then rows again(row.filter).
If you move the logic to a computed, you can simplify the logic and improve the performance. A computed will keep the data cached and update as needed, so if you change the value of search it will re-compute, but if an unrelated variable changes then it wouldn't, and the template wouldn't have to recalculate the values again. In your code, it seems like there's no much for other values that might change, but good practice anyway.
here's what that might look like (untested code)
computed: {
tableData() {
return this.filteredRow.map(row => {
const cols = [];
this.tableHead.forEach(item => {
let value = "";
if (col.Name === row.Key) {
let value = item.Value.replace(search, `<span style='color:#1D7CA7'><b>${search}</b></span>`)
}
cols.push(value)
});
return {...row, cols};
})
}
},
<table v-if="showTable">
<tr>
<th v-for="head in tableHead" :key="head.Name">
{{ head.Name }}
</th>
</tr>
<tr
v-for="row in tableData"
:key="row.Key"
class="data"
#click="zoomToFeatureExtent(row)"
>
<td v-for="(cell, i) in row.cols" :key="i">
<p v-html="cell"></p>
</td>
</tr>
</table>

Filtering an Array of Nested Objects Using Angular Pipes

I am trying to filter nested array values along with object name is getting filter when I search number values like age, weight, height and mobile not getting anything. If match with the searchText it must return a specific value along with the object if not then return full array instead of not found message.
pipe.ts
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'FilterPipe',
})
export class FilterPipe implements PipeTransform {
transform(items: any, filter: any, defaultFilter: boolean): any {
if (!filter || !Array.isArray(items)) {
return items;
}
if (filter && Array.isArray(items)) {
let filterKeys = Object.keys(filter);
if (defaultFilter) {
return items.filter(item =>
filterKeys.reduce((x, keyName) =>
(x && new RegExp(filter[keyName], 'gi').test(item[keyName])) || filter[keyName] == "", true));
}
else {
return items.filter(item => {
return filterKeys.some((keyName) => {
return new RegExp(filter[keyName], 'gi').test(item[keyName]) || filter[keyName] == "";
});
});
}
}
}
}
app.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'testJobNestedArray';
searchableList: any;
searchText : string = "";
users : any;
constructor() { }
ngOnInit() {
this.users = [
{
name: 'manpreet',
data:{
age: 25,
weight: 65,
height: 5.6,
mobile: [9780698969, 6895741258]
}
},
{
name: 'abdul',
data: {
age: 26,
weight: 80,
height: 6.0,
mobile: [3698541258]
}
},
{
name: 'onkar',
data: {
age: 28,
weight: 70,
height: 5.8,
mobile: [8569741236, 6528965478]
}
}
]
// this.searchableList = ['name', 'age', 'weight', 'height', 'mobile']
console.log('this.users', this.users)
}
}
app.html
<router-outlet></router-outlet>
<div>
<h1>Nested Array Table</h1>
<div class="md-form">
<input type="text" [(ngModel)]="searchText" class="form-control" name="searchText" placeholder="Search" />
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>#ID</th>
<th>Name</th>
<th>Age</th>
<th>Weight</th>
<th>Height</th>
<th>Mobile</th>
</tr>
</thead>
<tbody>
<tr
*ngFor="let item of users| FilterPipe:
{name: searchText, age: searchText, weight: searchText, height: searchText, mobile: searchText}; let i = index">
<td>{{ i+1 }}</td>
<td>{{ item.name }}</td>
<th>{{ item.data.age }}</th>
<td>{{ item.data.weight }}</td>
<td>{{ item.data.height}}</td>
<td>{{ item.data.mobile }}</td>
</tr>
</tbody>
</table>
</div>
In the FilterPipe class, kindly change the else statement as below
return items.filter(item => {
return filterKeys.some((keyName) => {
return new RegExp(filter[keyName], 'gi').test(item[keyName]) ||
new RegExp(filter[keyName], 'gi').test(item.data[keyName]) ||
filter[keyName] == "";
});
});
The issue was the the properties under data object was missed. see working code in below link
stackblitz

Angular 8 => Condition : show an else value

I'm trying to filter a default value if no result is found on my query.
I tried using ng-template, but I simply don't manage to do it.
Rather than writing, here are images to better understand :
This is my successful filter : it correctly shows my datas once I filter them in the search box.
However if I try to enter an invalid data as done below :
It simply returns me an empty html table.
Which is not what I'd like to achieve. I'd like it instead to show a message saying : no data found.
How can I do it please?
And here is my source code :
Component :
import {Component, OnInit} from '#angular/core';
import {IProduct} from './product';
#Component({
selector: 'pm-products',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {
///////////////////////////////////// PROPERTIES //////////////////////////////////////
// String Interpolation
pageTitle = 'Product List';
// Property binding
imageWidth = 50;
imageMargin = 2;
// Event binding
showImage = false;
// Two-way binding
// listFilter = 'cart';
// Filter products
private _listFilter: string;
// Filter Products Array
filteredProducts: IProduct[];
///////////////////////////////////// CONSTRUCTOR //////////////////////////////////////
constructor() {
this.filteredProducts = this.products;
this.listFilter = 'cart';
}
/////////////////////////////////// GETTERS/SETTERS ///////////////////////////////////
get listFilter(): string {
return this._listFilter;
}
set listFilter(value: string) {
this._listFilter = value;
/***
* If there is a list of filtered value, show the list of the filtered values => this.performFilter(this.listFilter)
* Else, (if there is no filtered) return the whole set of products
*/
this.filteredProducts = this.listFilter ? this.performFilter(this.listFilter) : this.products;
}
/////////////////////////////////////// METHODS ///////////////////////////////////////
// Get Products
products: IProduct[] = [
{
productId: 2,
productName: 'Garden Cart',
productCode: 'GDN-0023',
releaseDate: 'March 18, 2019',
description: '15 gallon capacity rolling garden cart',
price: 32.99,
starRating: 4.2,
imageUrl: 'assets/images/garden_cart.png'
},
{
productId: 5,
productName: 'Hammer',
productCode: 'TBX-0048',
releaseDate: 'May 21, 2019',
description: 'Curved claw steel hammer',
price: 8.9,
starRating: 4.8,
imageUrl: 'assets/images/hammer.png'
},
];
performFilter(filterBy: string): IProduct[] {
/**
* filterBy result => to lower case. => case insensitive comparison.
* Return a new array of the filtered productS by the product name,
* by checking if that product name given is an index of the an element in the product array.
*/
filterBy = filterBy.toLowerCase(); // 1.
return this.products.filter((product: IProduct) => product.productName.toLowerCase().indexOf(filterBy) !== - 1); // 2.
}
toggleImage = (): void => {
this.showImage = !this.showImage;
}
////////////////////////////////// LIFECYCLE HOOKS ///////////////////////////////////
ngOnInit(): void {
console.log('hello');
}
}
HTML
<div class="card">
<div class="card-header">{{pageTitle}}</div>
<!-- CARD -->
<div class="card-body">
<div class="row">
<div class="col-md-2"> Filter by:</div>
<div class="col-md-4">
<input [(ngModel)]="listFilter" type="text"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<h4>Filtered by: {{listFilter}} </h4>
</div>
</div>
<!-- ./CARD -->
<!-- TABLE -->
<div class="table-responsive">
<table *ngIf="products && products.length" class="table">
<thead>
<tr>
<th>
<button (click)="toggleImage()" class="btn btn-primary">{{showImage ? "Hide" : "Show"}} image</button>
</th>
<th>Product</th>
<th>Code</th>
<th>Available</th>
<th>Price</th>
<th>5 Star Rating</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let product of filteredProducts">
<td><img *ngIf="showImage" [src]="product.imageUrl" [title]="product.productName"
[style.width.px]="imageWidth" [style.margin.px]="imageMargin" alt=""></td>
<td>{{product.productName}}</td>
<td>{{product.productCode | lowercase | convertToSpaces: '-'}}</td>
<td>{{product.releaseDate}}</td>
<td>{{product.price | currency: 'EUR':'symbol':'2.2-2'}}</td>
<td>{{product.starRating}}</td>
</tr>
</tbody>
</table>
</div>
<!-- ./TABLE -->
</div>
</div>
Please take care of yourself.
Maybe just include an *ngIf="!filteredProducts.length" wherever you'd like to show your message.
ex.
<tr *ngFor="let product of filteredProducts">
//Your table stuff
</tr>
<tr *ngIf="!filteredProducts.length">
<td colspan="6">No data found</td>
</tr>
You have to add tr contains string no data found after ngfor tr
<tr *ngFor="let product of filteredProducts">
<td><img *ngIf="showImage" [src]="product.imageUrl" [title]="product.productName"
[style.width.px]="imageWidth" [style.margin.px]="imageMargin" alt=""></td>
<td>{{product.productName}}</td>
<td>{{product.productCode | lowercase | convertToSpaces: '-'}}</td>
<td>{{product.releaseDate}}</td>
<td>{{product.price | currency: 'EUR':'symbol':'2.2-2'}}</td>
<td>{{product.starRating}}</td>
</tr>
<tr *ngIf="filteredProducts.length === 0 " >
<td colspan="6" >Your message here </td>
</tr>
You can simply add a row that appears only when the list is empty and the search bar has any value. like this:
<tr *ngFor="let product of filteredProducts">...</tr>
<tr col-span ="6" *ngIf="!filteredProducts.length && listFilter.length"> uh oh,
could not find what you searched for</tr>

Angular 6 filter multiple table column

I have a table with bank details and I want to filter all across the table column based on the search field input. But its not working, can someone please help me debug the issue. When I'm typing something on the search field then the entire table data is disappearing.
<div>
<div>
<select (change)="OnSelectedCity($event)">
<option *ngFor="let cityObj of cityList" [value]="cityObj.value">{{cityObj.displayValue}}</option>
</select>
<input type="text" [(ngModel)]="filterText" [formControl]="filterInput" />
</div>
<div>
<table>
<thead>
<tr>
<th *ngFor="let header of tableHeader">{{header}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let bank of bankList | filterdata: filterText">
<td>{{bank.ifsc}}</td>
<td>{{bank.bank_id}}</td>
<td>{{bank.branch}}</td>
<td>{{bank.address}}</td>
<td>{{bank.city}}</td>
<td>{{bank.district}}</td>
<td>{{bank.state}}</td>
</tr>
</tbody>
</table>
</div>
</div>
transform(items: Array<any>, searchText: string): any {
if (searchText !== undefined) {
return items.filter(item => {
const filter = Object.keys(item);
filter.forEach(element => {
if (item[element].toLowerCase().indexOf(searchText.toLowerCase()) === -1) {
return false;
}
return true;
});
});
} else {
return items;
}
}
You just have a simple error in your pipe. The error lies in the .forEach call, because it's not possible to stop or break a forEach loop, see the docs.
After the adjustment your pipe could look like this:
transform(items: Array<any>, searchText: string): any {
if (searchText) {
return items.filter(item => {
const filter = Object.keys(item);
// Array.some() returns true if at least one entry meets the given condition
return filter.some(
key => item[key].toLowerCase().indexOf(searchText.toLowerCase()) !== -1
)
});
}
return items;
}

Pipes angular 2 filter multiple columns considering null value

I am using pipe to filter data on three different columns in a data table.
The names of the column are category, name,department.
I am passing the arguments as follows:
<table class="table table-striped" [mfData]="listdata | dataFilter : filtername : filterDep : filterCategory">
I have *ngFor loop:
<tr *ngFor="let item of mf.data; let i =index">
<td>
{{i+1}}
</td>
<td>{{ item.name }}</td>
<td>{{item.department}}</td>
<td>{{item.category}}</td>
</tr>
The code which sets the query variable is
<th colspan="2">
Filter by name:
<input class="form-control" [(ngModel)]="filtername" (input)="change()" />
</th>
<th colspan="1">
Filter by Department:
<input class="form-control" [(ngModel)]="filterDep" (input)="change()" />
</th>
<th colspan="1">
Filter by Category:
<input class="form-control" [(ngModel)]="filterCategory" (input)="change()" />
</th>
I have data in which some of the category is null.
The pipe which I am using is:
#Pipe({
name: "dataFilter"
})
export class DataFilterPipe implements PipeTransform {
transform(array: any[], query: string, query2: string, query3: string): any {
let notNullCategory = _.reject(array, ['category', null]);
if (query || query2 || query3) {
return _.filter(notNullCategory, row => ((row.name.indexOf(query) > -1) && (row.department.indexOf(query2) > -1) && (row.category.indexOf(query3) > -1)));
}
return array;
}
}
This way if the user makes the search with some name only the rows which have non null values of category are considered.
I want to take in consideration the rows with null values as well , but I am unable to do so.
If I change my pipe to:
#Pipe({
name: "dataFilter"
})
export class DataFilterPipe implements PipeTransform {
transform(array: any[], query: string, query2: string, query3: string): any {
if (query || query2 || query3) {
return _.filter(array, row => ((row.name.indexOf(query) > -1) && (row.department.indexOf(query2) > -1) && (row.category.indexOf(query3) > -1)));
}
return array;
}
}
I get the error:
Cannot read property 'indexOf' of null
Any help would be greatly appreciated.
you need to check if each row's name, department or catigory is null:
#Pipe({
name: "dataFilter"
})
export class DataFilterPipe implements PipeTransform {
transform(array: any[], query: string,query2: string,query3: string): any {
if(query || query2 || query3)
{
return _.filter(array, row =>
{
var hasName = row.name && row.name.indexOf(query) > -1;
var hasDepartment = row.department && row.department.indexOf(query2) > -1
var hasCategory = row.category && row.category.indexOf(query3) > -1
return hasName && hasDepartment && hasCategory;
});
}
return array;
}
}
If you want to filter category only when category is not null you can do this:
#Pipe({
name: "dataFilter"
})
export class DataFilterPipe implements PipeTransform {
transform(array: any[], name: string, department: string, category: string): any {
if (name || department || category) {
_.filter(array, row => ((row.name.indexOf(name) > -1)
&& (row.department.indexOf(department) > -1)
&& (row.category ? row.category.indexOf(category) > -1 : true))
);
}
return array;
}
}
Try this, it's working fine for me when i search for multiple columns.
transform(categories: any, searchText: any): any {
if(searchText == null) return categories;
return categories.filter(function(category){
var name_search=category.name.toLowerCase().indexOf(searchText.toLowerCase()) >
-1;
var email_search=category.email.toLowerCase().indexOf(searchText.toLowerCase())
> -1;
return name_search || email_search;
})
}
Previously i was having issues when i use this "import * as _ from "lodash";
" that is,the data was not displaying when i load the page but when i search the data, filter was working fine and data also displaying..

Categories