How to create pipe to filter list in Angular2 - javascript

I'm facing problem with creating the pipe to filter list from input.
What I would like to have is something like this: http://www.w3schools.com/howto/howto_js_filter_lists.asp
Is there anybody who can help me with creating pipe for this?
Update, I changed the code with one proposition and it is still not working with my part of code.
Some parts from my code:
component.html:
<input id="desc" type="text" placeholder="Alarm name" [(ngModel)]="desc">
<ul>
<li *ngFor="let name of names" [style.display]="(name | search : desc) ? 'block' : 'none'">
{{ name }}
</li>
</ul>
<div class="alarm-list-item" *ngFor="let alarm of alarmsList" [style.display]="(alarm.Description | search : desc) ? 'block' : 'none'">
{{alarm.Description }}
</div>
alarmList is an array:
enter image description here
search.pipe.ts also I had to change the pipe code as "contains" doesn't work and I change type to any:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'search'
})
export class SearchPipe implements PipeTransform {
transform(list: any, searchText: any): any {
for(let i=0; i<list.length; i++){
if(list[i].includes(searchText)){
return true;
}
}
return false;
}
}

You're not supposed to do that with a Pipe I believe.
What you want is to perform a pipe operation on your itemssource 'alarmlist' which angular 2 won't allow.
What you could do though is something like this, which I'd not advise since it's ugly code:
https://plnkr.co/edit/3e6cFSBFIf0uYYIgKNZ8?p=preview
My advice would be to make another property: filteredAlarmList which you would edit in the (change) event of the <input> or the setter of your desc property and then rewrite your ngFor like *ngFor="let alarm of filteredAlarmList"
If you need this kind of filtering multiple times you could extract all of this to a seperate components. e.g.: FilterListComponent which will take 2 inputs: searchText and List (in your case AlarmList)

something like this could help
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'search',
pure: true
})
export class SearchPipe implements PipeTransform {
transform(value: any, args:string=''): any {
console.log(value);
if(!value) return value;
else {
if(value.toLowerCase().indexOf(args.toLowerCase())!= -1) {
return value;
}
}
}
}
In your component
#Component({
selector: 'my-app',
template: `
<input type="text" [(ngModel)]="filterValue">
<ul>
<li *ngFor="let name of names">{{name | search:filterValue}}</li>
</ul>`,
})
export class AppComponent {
names:Array<string> = ['Agnes','Adele','Binny','Bob'];
constructor(){
}
}

Related

How do you filter an Observable with form input?

I have a component with this "countries$" variable:
countries$!: Observable<Country[]>;
that I'm populating with this data in an "ngOnInit" like this:
ngOnInit(){
this.countries$ = this.apiService.getAllCountries();
}
and I'm accessing this variable/Observable in the html template like this:
<div>
<app-country-card *ngFor="let country of countries$ | async" [country]="country"></app-country-card>
</div>
I want to include a search bar that filters the countries down to whatever is typed in.
I thought I could use the filter function inside a pipe like this:
searchFilterCountries(searchTerm: string){
this.countries$.pipe(filter((country: any) => country.name.common.toLowerCase().includes(searchTerm.toLowerCase())))
}
and put the input in the html template like this:
<input type="text" class="form-control" (input)="searchFilterCountries($event.target.value)"/>
so that the filter function would fire every time theres an input, narrowing down the list of countries on display.
This doesn't work however. I'm getting the typescript error:
Object is possibly 'null'.ngtsc(2531)
Property 'value' does not exist on type 'EventTarget'.ngtsc(2339)
Then I found a "sample" of a working filtered list here on Material UI
https://material.angular.io/components/autocomplete/examples (The FILTER one)
I attempted to implement this and came up with this code:
export class HomeComponent {
countries$!: Observable<Country[]>;
myControl = new FormControl('');
constructor(private apiService: ApiService) { }
ngOnInit(){
this.countries$ = this.apiService.getAllCountries();
}
private _filter(value: string): Observable<Country[]> {
const filterValue = value.toLowerCase();
return this.countries$.pipe(filter(option => option.name.common.toLowerCase().includes(filterValue))) <----ERROR #2
}
}
It doesn't work however. I think because the values are observables, not the data inside the observable.
I have squiggly lines showing a TS error under the under the "name" property in "option.name.common" saying:
option.name.common TS error
Property 'name' does not exist on type 'Country[]'
If I do this instead though:
option => option[0].name.common.toLowerCase().includes(filterValue)))
the error goes away, but I wouldn't be able to search all the values if I did that.
Am I on the right track here? Am I using the right operators? How do I fix the TS errors? I'm new to angular and don't know all the operators available. If I use mergeMap/switchMap will that solve my problem? If I do fix the typescript errors would it even work? Or is my approach wrong?
Can somebody help me get this working?
I would like to expand on your current code and suggest some changes like this:
export class HomeComponent {
allCountries: Country[] = [];
countries$!: Observable<Country[]>;
myControl = new FormControl('');
constructor(private apiService: ApiService) {}
ngOnInit() {
this.apiService
.getAllCountries()
.subscribe((countries) => (this.allCountries = countries));
this.countries$ = combineLatest({
searchTerm: this.myControl.valueChanges.pipe(startWith('')),
countries: this.apiService
.getAllCountries()
.pipe(tap((countries) => (this.allCountries = countries))),
}).pipe(map(({ searchTerm }) => this._filter(searchTerm)));
}
private _filter(value: string | null): Country[] {
if (value === null) {
return this.allCountries;
}
const filterValue = value?.toLowerCase();
return this.allCountries.filter((country) =>
country.name.common.toLowerCase().includes(filterValue)
);
}
}
So we're keeping the original country list in a separate variable, and we are using the form control's valueChange event to filter the countries that we need to display.
The template should look like this:
<input type="text" [formControl]="myControl" />
<div *ngFor="let country of countries$ | async">
<div>Name: {{ country.name.common }}</div>>
</div>
Example pipe
import { Pipe, PipeTransform } from '#angular/core';
import { Country } from './country';
#Pipe({
name: 'filterList',
})
export class FilterListPipe implements PipeTransform {
transform(countries: Country[]|null, searchText: string): Country[] {
if(!countries) return []
return countries.filter(country=>country.name.indexOf(searchText) != -1);
}
}
app.component.html
<form [formGroup]="controlsGroup">
<input type="text" formControlName="searchInput"/>
<div *ngFor="let country of countries | async | filterList:searchText">
<div>Name: {{country.name}}</div>
<div>Ranking: {{country.ranking}}</div>
<div>Metric: {{country.metric}}</div>
</div>
</form>
app.component.ts
import { Component } from '#angular/core';
import { FormBuilder, FormControl, FormGroup } from '#angular/forms';
import { Observable, of } from 'rxjs';
import { Country } from './country';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'piper-example-app';
searchText = ''
controlsGroup: FormGroup
constructor(public fb:FormBuilder){
this.controlsGroup = fb.group({
searchInput: new FormControl('')
})
this.controlsGroup.get('searchInput')?.valueChanges.subscribe(value => this.searchText=value)
}
countries: Observable<Country[]> = of([{
name: 'United States of America',
ranking: 1,
metric: 'burgers per capita'
},
{
name: 'China',
ranking: 9000,
metric: 'power level lower bound'
}])
}
Admittedly I'm doing a few things that are "dirty" here where filtering the incoming observable stream of arrays of countries might be a bit more efficient. Also note you'd need to still expand the filter function to check all the properties (can use for(prop in obj) type loop to iterate over all properties to see if any of them matches the searchText or adjust the criteria as see fit.
Bit more of a complete example showing the filter part with different types of properties being filtered slightly differently:
filter-list.pipe.ts (alternative)
import { Pipe, PipeTransform } from '#angular/core';
import { Country } from './country';
#Pipe({
name: 'filterList',
})
export class FilterListPipe implements PipeTransform {
transform(countries: Country[]|null, searchText: string): Country[] {
if(!countries) return []
return countries.filter(country => {
let foundMatch = false;
let property: keyof typeof country
for(property in country) {
if(typeof country[property] === 'string') {
if((country[property] as string).indexOf(searchText) != -1)
foundMatch = true
}else {
if((country[property] as number) == parseInt(searchText))
foundMatch = true
}
}
return foundMatch
});
}
}

Angular: Passing data binding as argument is not working

So far, this approach works:
<div class="container" *ngFor="let it of item | itemFilter:'AnyString'">
However, with data binding it does not work as follows:
<div class="container" *ngFor="let it of item | filter:{{filterstr}}">
<div class="container" *ngFor="let it of item | filter:'{{filterstr}}'">
<div class="container" *ngFor="let it of item | filter:{{filterSearch.toString()}}">
My pipe function is as follows:
import {Pipe, PipeTransform} from '#angular/core'
#Pipe({name:'filter'})
export class ItemsPipe implements PipeTransform
{
transform(items:Array<any>, filterString:String):any{
return items.filter(item=>{
if(item.name.includes(filterString))
return item
})
}
}
My component module:
export class HomeComponent implements OnInit {
filterstr: String;
constructor() { }
ngOnInit() {
this.filterstr = 'anything';
}
}
I thought I was accidentally passing null in the parameter, yet even after I initialize 'filterSearch', it doesn't work.
Additionally, I would have to filter the array list by keys as well. What parameter type should I set for the transform function?
Many thanks in advance!!
Try:
<div class="container" *ngFor="let it of item | filter:filterstr">
Try like this
template.html
<div class="container" *ngFor="let it of item | filter:filterstr">
pipe.ts
#Pipe({name:'filter'})
export class ItemsPipe implements PipeTransform
{
transform(items:Array<any>, filterString:String):any{
if(filter === '') {
return items;
} else {
return items.filter(item=>{ if(item.name.includes(filterString))
return item
})
}
}
}
component.ts
filterstr: String = '';
Initially your search input will empty so pipe will return all items.
As soon as you start typing in input pipe will return filtered items;

stringLowerCase filter to apply on ngFor

I'm defining a pipes example in my application, which transforms the uppercase string to lowercase ex: 'Activities' => 'activities'. And the data is in the Array format and I am applying this filter on *ngFor. It returns me an error saying 'toLowerString is not a function', Please help me understand where I am going wrong.
Demo link
<li *ngFor="let caption of captions">
{{caption | lowercase }}
</li>
<li *ngFor="let caption of captions">
{{caption | uppercase }}
</li>
<li *ngFor="let caption of captions">
{{caption | titlecase }}
</li>
You have to apply your custom pipe like below it will work.
<ul>
<li *ngFor="let caption of captions ">
{{caption | stringLowerCase }}
</li>
</ul>
And in your pipe return after value tranform like below.
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'stringLowerCase'
})
export class LowerCasePipe implements PipeTransform {
transform(value: string) {
console.log(value.toString().toLowerCase());
return value.toLowerCase();
}
}
Here is forked solution
You can not use value.toLowerCase(), because value seems tobe an array not a string. So The value must be type Array not string and you must return an array not console.log.
try this:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'stringLowerCase'
})
export class LowerCasePipe implements PipeTransform {
transform(value: Array<string>) {
return value.map((x)=>{ return x.toLowerCase() })
}
}
Notice that .toLowerCase() is usable only on string data type. Which to said you need to make sure that the data was a string with: .toString()
value.toString().toLowerCase()
With that being said as Thanveer Shah have been said in the comment you can use | lowercase which are inbuilt to transform into a string.
See Fork for both versions being in used.
Clarification:
Intentionally used the following code:
export class LowerCasePipe implements PipeTransform {
transform(value) {
console.log(value.toString().toLowerCase());
for (let i = 0; i < value.length; i++) {
value[i] = value[i].toString().toLowerCase();
}
return value;
}
}
to show that the data was an array.
But if you already understand that it was an array you could simply use:
export class LowerCasePipe implements PipeTransform {
transform(value: Array<string>) {
return value.map((x)=>{ return x.toLowerCase() })
}
}
as in the other answer by Ferhad Othman.
<li *ngFor="let caption of captions">
{{caption | lowercase }}
</li>

How to implement Custom Filter Pipe with *ngFor in Angular 4?

I am using a custom filter pipe on a <li></li> tag. The user will just type a term in the <input> and the list is filtered like a search function.
test.component.html
<form id="filter">
<label>Filter people by name:</label>
<input type="text" [(ngModel)]="term" />
</form>
<ul id="people-listing">
<li *ngFor="let person of people | filter:term">
<div class="single-person">
<span [ngStyle]="{background: person.belt}">{{person.belt}} belt</span>
<h3>{{person.name}}</h3>
</div>
</li>
</ul>
test.component.ts
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { FilterPipe } from '../filter.pipe';
#Component({
selector: 'app-directory',
templateUrl: './test.component.html',
styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit {
people = [
{name: "Yoshi", belt: "black"},
{name: "Ryu", belt: "red"},
{name: "Crystal", belt: "purple"}
];
constructor() {
}
ngOnInit() {
}
}
filter.pipe.ts
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(people: any, term: any): any {
//check if search term is undefined
if(term === undefined) return people;
//return updates people array
return people.filter(function(thisperson){
return thisperson.name.toLowerCase().includes(term.toLowerCase());
})
}
}
Whenever I type a name in the <input> tag, the list with *ngFor is NOT filtered according to the typed word.
I am using Angular 4.1.1.
Do you have any idea how to fix the code above? Thanks.
bind your input with name property.
<form id="filter">
<label>Filter people by name:</label>
<input type="text" name="people" [(ngModel)]="term" />
</form>
Also make sure you have add FilterPipe to declarations of NgModule.

Filter and search unordered list Angular2

I'm looking for a way to filter an unordered list using an input field with angular.
I have a component which builds an unordered list on page load with data fetched from a JSON file, using the *ngFor directive, it does so by using a service to get the actual data. Here's the code for the component in question:
operation-catalogue.component.ts:
import { Component, OnInit, Input } from '#angular/core';
import { OperationService } from "./operation.service";
#Component({
selector: 'operation-catalogue-component',
templateUrl: './operation-catalogue.component.html',
styleUrls: ['./operation-catalogue.component.css'],
})
export class OperationCatalogueComponent implements OnInit {
operationCatalogue = [];
constructor(private operationService: OperationService) { }
ngOnInit() {
//Get the items to put in the list...
this.operationCatalogue = this.operationService.getOperations();
}
}
operation-catalogue.component.html:
<div id="search-box-div">
<div id="search-field" class="top-div">
<input #input type="text" placeholder="Filter">
</div>
</div>
<ul>
<!-- generate list, name only -->
<li *ngFor="let operation of operationCatalogue">
<label>{{operation.name}}</label>
</li>
</ul>
I've left out the service on purpose because it works as intended and not needed for this example.
What I want to do is be able to filter the list that is generated using the html input element.
I've tried to have a look at past questions here on stack overflow but they all seem to be outdated and using methods that Angular2 no longer supports.
How can I accomplish this goal with a modern method?
You will need to use a Pipe for this. Here is an example I've created a gist on Github since it's rather long.
operation-catalogue.component.ts:
import { Component, OnInit, Input } from '#angular/core';
import { OperationService } from "./operation.service";
#Component({
selector: 'operation-catalogue-component',
templateUrl: './operation-catalogue.component.html',
styleUrls: ['./operation-catalogue.component.css'],
})
export class OperationCatalogueComponent implements OnInit {
operationCatalogue = [];
objectsFilter = {name: ''};
constructor(private operationService: OperationService) { }
ngOnInit() {
//Get the items to put in the list...
this.operationCatalogue = this.operationService.getOperations();
}
}
operation-catalogue.component.html:
<div id="search-box-div">
<div id="search-field" class="top-div">
<input #input type="text" placeholder="Filter" [(ngModel)]="objectsFilter.name">
</div>
</div>
<ul>
<!-- generate list, name only -->
<li *ngFor="let operation of operationCatalogue | filterBy: {name: objectsFilter.name};">
<label>{{operation.name}}</label>
</li>
</ul>
Plunker example:
https://embed.plnkr.co/xbW6nbkQZfwudOAnrEXl/
You can use indexOf feature of Angular2+,
<li *ngIf="searchElement === '' || (label | lowercase).indexOf(searchElement | lowercase) != -1" >
This will work without any extra pipe. indexOf will return any number if it found match text.

Categories

Resources