Subscribe

Angular search pipe for a list

โœ๏ธ

Adding a angular search pipe to filter a list of items

11 Jan, 2021 ยท 6 min read

We'll be creating a live-search function for an Angular list in today's article.

I plan to have a list rendered in Angular and an input type above it. If we type in this input, we should see the list contents change.

You can see the result in this GIF.

Angular search pipe for a list

Setting up the project

We will be using my master Angular project for this project since we don't want to set up Angular from Scratch.

Note: Check out this article if you plan to install Angular from scratch

Download the starter project or install it yourself, then you can open your terminal and run ng serve.

Creating the list

The next part is to create a new component. This is the List component. We can use the Angular generator to generate this component for us.

ng generate component list

You can then add this list component to your app.component.html file.

<li><a routerLink="/welcome" routerLinkActive="active">Welcome</a></li>
<li><a routerLink="/list" routerLinkActive="active">List</a></li>

Then we need to active the route in our routing file. Open up the app-routing.module.ts.

You'll need to import the Component on the top.

import { ListComponent } from './list/list.component';

And add the following line as a route.

{ path: 'list', component: ListComponent },

Now we should be able to run our application and visit the /list route.

The next thing we want to add is our data, so open up the list.component.ts file and add the following data set.

people = [
  {
    firstname: 'Chris',
    lastname: 'Bongers',
  },
  {
    firstname: 'Peter',
    lastname: 'Rabbit',
  },
  {
    firstname: 'Donald',
    lastname: 'Duck',
  },
  {
    firstname: 'Lady',
    lastname: 'Gaga',
  },
];

We want to show this list on the HTML side, so we need to render it in our HTML file.

<ul>
  <li *ngFor="let person of people">
    {{ person.firstname }} {{ person.lastname }}
  </li>
</ul>

If we run this code, we should see our list rendered.

Angular rendered list

We need to have a search input on top of this list. This needs to be connected to a model so we can use the value.

First, we need to enable the ReactiveForms module.

We can add it to our app.module.ts file.

// Other imports
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  declarations: [...],
  imports: [ReactiveFormsModule, ...],
  providers: [],
  bootstrap: [AppComponent],
})

With this in place, we can create the form in our list.component.ts.

The first thing we add is a variable for our searchForm.

searchForm;

Then we modify the constructor to load the formBuilder and create the search form.

constructor(private formBuilder: FormBuilder) {
  this.searchForm = this.formBuilder.group({
    search: '',
  });
}

This will create a form we can use in our HTML file.

Add the following form to the top of our list.

<form [formGroup]="searchForm">
  <input formControlName="search" />
</form>

Note: The formControlName references the formBuilder group name.

Generating the Angular Search Pipe

To generate this pipe, we can run the following command.

ng generate pipe SearchFilter

This will generate and register our pipe for us.

To use this pipe, we need to add it to the ngFor on the list we created in list.component.ts.

<li *ngFor="let person of people | searchFilter: searchForm.value.search"></li>

As you can see above, we add the searchFilter pipe and pass the argument of the search field value.

Now we need to make sure this search filter pipe will return only matching results.

Let's create the outline for this filter first. Our filter has two parameters, one being the input (value) and one being the search (string).

We use typescript to define what our value looks like. In this case, it's an array with an object in it.

Then you'll see the :, which defines the output for this transform function.

transform(
  value: { firstname: string; lastname: string }[],
  search: string
): { firstname: string; lastname: string }[] {
  //return something
}

Now, let's create the actual function.

We start by checking if the value is set.

if (value) {
  // Do something
}

If we get a value, we need to create a regular expression to match against based on the search parameter.

const regexp = new RegExp(search, 'i');

Then we also want to get all the property's keys.

const properties = Object.keys(value[0]);

What the above does is get the keys for the first array element.

// ['firstname', 'lastname'];

Then it's time to return an actual value.

return [
  ...value.filter((item) => {
    return properties.some((property) => regexp.test(item[property]));
  }),
];

This is a bit of a tricky one. We return an array []. Inside this array, we use the spread operator to get a copy of the value array.

We use the JavaScript filter method to filter the values. Inside the filter, we return a boolean because we use the JavaScript some method on the property array.

To demo what will happen if we search for chris.

We'll get in the loop and ask if one of the properties (firstname/lastname) matches the regular expression based on the search string.

In the first case, this is true so that the result will be returned as yes. In the other ones, it's false.

The result is an array of 1 object, Chris Bongers.

Angular search list result

The entire search pipe will look as follows.

import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
  name: 'searchFilter',
})
export class SearchFilterPipe implements PipeTransform {
  transform(
    value: { firstname: string, lastname: string }[],
    search: string
  ): { firstname: string, lastname: string }[] {
    if (value) {
      const regexp = new RegExp(search, 'i');
      const properties = Object.keys(value[0]);
      return [
        ...value.filter((item) => {
          return properties.some((property) => regexp.test(item[property]));
        }),
      ];
    }
  }
}

You can also find this project on GitHub.

Thank you for reading, and let's connect!

Thank you for reading my blog. Feel free to subscribe to my email newsletter and connect on Facebook or Twitter

Spread the knowledge with fellow developers on Twitter
Tweet this tip
Powered by Webmentions - Learn more

Read next ๐Ÿ“–

Angular dynamic classes using ngClass

31 Mar, 2021 ยท 2 min read

Angular dynamic classes using ngClass

Angular dynamically change form validators

30 Mar, 2021 ยท 3 min read

Angular dynamically change form validators

Join <html><head><script data-ezscrex='false' data-cfasync='false' data-pagespeed-no-defer>var __ez=__ez||{};__ez.stms=Date.now();__ez.evt={};__ez.script={};__ez.ck=__ez.ck||{};__ez.template={};__ez.template.isOrig=true;__ez.queue=function(){var e=0,i=0,t=[],n=!1,s=[],r=[],o=!0,a=function(e,i,n,s,r,o,a){var l=this;this.name=e,this.funcName=i,this.parameters=null===n?null:n instanceof Array?n:[n],this.isBlock=s,this.blockedBy=r,this.deleteWhenComplete=o,this.isError=!1,this.isComplete=!1,this.isInitialized=!1,this.proceedIfError=a,this.isTimeDelay=!1,this.process=function(){u("... func = "+e),l.isInitialized=!0,l.isComplete=!0,u("... func.apply: "+e);var i=l.funcName.split("."),n=null;i.length>3||(n=3===i.length?window[i[0]][i[1]][i[2]]:2===i.length?window[i[0]][i[1]]:window[l.funcName]),null!=n&&n.apply(null,this.parameters),!0===l.deleteWhenComplete&&delete t[e],!0===l.isBlock&&(u("----- F'D: "+l.name),f())}},l=function(e,i,t,n,s,r,o){var a=this;this.name=e,this.path=i,this.async=s,this.defer=r,this.isBlock=t,this.blockedBy=n,this.isInitialized=!1,this.isError=!1,this.isComplete=!1,this.proceedIfError=o,this.isTimeDelay=!1,this.isPath=function(e){return"/"===e[0]&&"/"!==e[1]},this.getSrc=function(e){return void 0!==window.__ezScriptHost&&this.isPath(e)?window.__ezScriptHost+e:e},this.process=function(){a.isInitialized=!0,u("... file = "+e);var i=document.createElement("script");i.src=this.getSrc(this.path),!0===s?i.async=!0:!0===r&&(i.defer=!0),i.onerror=function(){u("----- ERR'D: "+a.name),a.isError=!0,!0===a.isBlock&&f()},i.onreadystatechange=i.onload=function(){var e=i.readyState;u("----- F'D: "+a.name),e&&!/loaded|complete/.test(e)||(a.isComplete=!0,!0===a.isBlock&&f())},document.getElementsByTagName("head")[0].appendChild(i)}},c=function(e,i){this.name=e,this.path="",this.async=!1,this.defer=!1,this.isBlock=!1,this.blockedBy=[],this.isInitialized=!0,this.isError=!1,this.isComplete=i,this.proceedIfError=!1,this.isTimeDelay=!1,this.process=function(){}};function d(e){!0!==h(e)&&0!=o&&e.process()}function h(e){if(!0===e.isTimeDelay&&!1===n)return u(e.name+" blocked = TIME DELAY!"),!0;if(e.blockedBy instanceof Array)for(var i=0;i<e.blockedBy.length;i++){var s=e.blockedBy[i];if(!1===t.hasOwnProperty(s))return u(e.name+" blocked = "+s),!0;if(!0===e.proceedIfError&&!0===t[s].isError)return!1;if(!1===t[s].isComplete)return u(e.name+" blocked = "+s),!0}return!1}function u(e){var i=window.location.href,t=new RegExp("[?&]ezq=([^&#]*)","i").exec(i);"1"===(t?t[1]:null)&&console.debug(e)}function f(){++e>200||(u("let's go"),m(s),m(r))}function m(e){for(var i in e)if(!1!==e.hasOwnProperty(i)){var t=e[i];!0===t.isComplete||h(t)||!0===t.isInitialized||!0===t.isError?!0===t.isError?u(t.name+": error"):!0===t.isComplete?u(t.name+": complete already"):!0===t.isInitialized&&u(t.name+": initialized already"):t.process()}}return window.addEventListener("load",(function(){setTimeout((function(){n=!0,u("TDELAY -----"),f()}),5e3)}),!1),{addFile:function(e,i,n,o,a,c,h,u){var f=new l(e,i,n,o,a,c,h);!0===u?s[e]=f:r[e]=f,t[e]=f,d(f)},addDelayFile:function(e,i){var n=new l(e,i,!1,[],!1,!1,!0);n.isTimeDelay=!0,u(e+" ... FILE! TDELAY"),r[e]=n,t[e]=n,d(n)},addFunc:function(e,n,o,l,c,h,u,f,m){!0===h&&(e=e+"_"+i++);var p=new a(e,n,o,l,c,u,f);!0===m?s[e]=p:r[e]=p,t[e]=p,d(p)},addDelayFunc:function(e,i,n){var s=new a(e,i,n,!1,[],!0,!0);s.isTimeDelay=!0,u(e+" ... FUNCTION! TDELAY"),r[e]=s,t[e]=s,d(s)},items:t,processAll:f,setallowLoad:function(e){o=e},markLoaded:function(e){if(e&&0!==e.length){if(e in t){var i=t[e];!0===i.isComplete?u(i.name+" "+e+": error loaded duplicate"):(i.isComplete=!0,i.isInitialized=!0)}else t[e]=new c(e,!0);u("markLoaded dummyfile: "+t[e].name)}},logWhatsBlocked:function(){for(var e in t)!1!==t.hasOwnProperty(e)&&h(t[e])}}}();__ez.evt.add=function(e,t,n){e.addEventListener?e.addEventListener(t,n,!1):e.attachEvent?e.attachEvent("on"+t,n):e["on"+t]=n()},__ez.evt.remove=function(e,t,n){e.removeEventListener?e.removeEventListener(t,n,!1):e.detachEvent?e.detachEvent("on"+t,n):delete e["on"+t]};__ez.script.add=function(e){var t=document.createElement("script");t.src=e,t.async=!0,t.type="text/javascript",document.getElementsByTagName("head")[0].appendChild(t)};__ez.dot={};__ez.queue.addFile('/detroitchicago/boise.js', '/detroitchicago/boise.js?gcb=195-0&cb=2', true, [], true, false, true, false);__ez.queue.addFile('/detroitchicago/memphis.js', '/detroitchicago/memphis.js?gcb=195-0&cb=21', true, [], true, false, true, false);__ez.queue.addFile('/detroitchicago/minneapolis.js', '/detroitchicago/minneapolis.js?gcb=195-0&cb=4', true, [], true, false, true, false);__ez.queue.addFile('/detroitchicago/rochester.js', '/detroitchicago/rochester.js?gcb=195-0&cb=13', false, ['/detroitchicago/memphis.js','/detroitchicago/minneapolis.js'], true, false, true, false);!function(){var e;__ez.vep=(e=[],{Add:function(i,t){__ez.dot.isDefined(i)&&__ez.dot.isValid(t)&&e.push({type:"video",video_impression_id:i,domain_id:__ez.dot.getDID(),t_epoch:__ez.dot.getEpoch(0),data:__ez.dot.dataToStr(t)})},Fire:function(){if(void 0===document.visibilityState||"prerender"!==document.visibilityState){if(__ez.dot.isDefined(e)&&e.length>0)for(;e.length>0;){var i=5;i>e.length&&(i=e.length);var t=e.splice(0,i),o=__ez.dot.getURL("/detroitchicago/grapefruit.gif")+"?orig="+(!0===__ez.template.isOrig?1:0)+"&v="+btoa(JSON.stringify(t));__ez.dot.Fire(o)}e=[]}}})}();</script><script data-ezscrex='false' data-cfasync='false' data-pagespeed-no-defer>!function(){function e(i){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(i)}__ez.pel=function(){var i=[];function t(t,o,d,_,n,r,a,s){if(__ez.dot.isDefined(t)&&0!=__ez.dot.isAnyDefined(t.getSlotElementId,t.ElementId)){void 0===s&&(s=!1);var p=parseInt(__ez.dot.getTargeting(t,"ap")),f=__ez.dot.getSlotIID(t),u=__ez.dot.getAdUnit(t,s),z=parseInt(__ez.dot.getTargeting(t,"compid")),g=0,c=0,l=function(i){if("undefined"==typeof _ezim_d)return!1;var t=__ez.dot.getAdUnitPath(i).split("/").pop();if("object"===("undefined"==typeof _ezim_d?"undefined":e(_ezim_d))&&_ezim_d.hasOwnProperty(t))return _ezim_d[t];for(var o in _ezim_d)if(o.split("/").pop()===t)return _ezim_d[o];return!1}(t);"object"==e(l)&&(void 0!==l.creative_id&&(c=l.creative_id),void 0!==l.line_item_id&&(g=l.line_item_id)),__ez.dot.isDefined(f,u)&&__ez.dot.isValid(o)&&("0"===f&&!0!==s||""===u||i.push({type:"impression",impression_id:f,domain_id:__ez.dot.getDID(),unit:u,t_epoch:__ez.dot.getEpoch(0),revenue:d,est_revenue:_,ad_position:p,ad_size:"",bid_floor_filled:n,bid_floor_prev:r,stat_source_id:a,country_code:__ez.dot.getCC(),pageview_id:__ez.dot.getPageviewId(),comp_id:z,line_item_id:g,creative_id:c,data:__ez.dot.dataToStr(o),is_orig:s||__ez.template.isOrig}))}}function o(){void 0!==document.visibilityState&&"prerender"===document.visibilityState||(__ez.dot.isDefined(i)&&i.length>0&&[i.filter((function(e){return e.is_orig})),i.filter((function(e){return!e.is_orig}))].forEach((function(e){for(;e.length>0;){var i=e[0].is_orig||!1,t=5;t>e.length&&(t=e.length);var o=e.splice(0,t),d=__ez.dot.getURL("/porpoiseant/army.gif")+"?orig="+(!0===i?1:0)+"&sts="+btoa(JSON.stringify(o));(void 0!==window.isAmp&&isAmp||void 0!==window.ezWp&&ezWp)&&void 0!==window._ezaq&&_ezaq.hasOwnProperty("domain_id")&&(d+="&visit_uuid="+_ezaq.visit_uuid),__ez.dot.Fire(d)}})),i=[])}return{Add:t,AddAndFire:function(e,i){t(e,i,0,0,0,0,0),o()},AddAndFireOrig:function(e,i){t(e,i,0,0,0,0,0,!0),o()},AddById:function(e,t,o,d){var _=e.split("/");if(__ez.dot.isDefined(e)&&3===_.length&&__ez.dot.isValid(t)){var n=_[0],r={type:"impression",impression_id:_[2],domain_id:__ez.dot.getDID(),unit:n,t_epoch:__ez.dot.getEpoch(0),pageview_id:__ez.dot.getPageviewId(),data:__ez.dot.dataToStr(t),is_orig:o||__ez.template.isOrig};void 0!==d&&(r.revenue=d),i.push(r)}},Fire:o,GetPixels:function(){return i}}}()}();__ez.queue.addFile('/detroitchicago/raleigh.js', '/detroitchicago/raleigh.js?gcb=195-0&cb=6', false, [], true, false, true, false);__ez.queue.addFile('/detroitchicago/tampa.js', '/detroitchicago/tampa.js?gcb=195-0&cb=5', false, [], true, false, true, false);</script> <script data-ezscrex="false" data-cfasync="false">(function(){if("function"===typeof window.CustomEvent)return!1;window.CustomEvent=function(c,a){a=a||{bubbles:!1,cancelable:!1,detail:null};var b=document.createEvent("CustomEvent");b.initCustomEvent(c,a.bubbles,a.cancelable,a.detail);return b}})();</script><script data-ezscrex="false" data-cfasync="false">__ez.queue.addFile('/detroitchicago/tulsa.js', '/detroitchicago/tulsa.js?gcb=195-0&cb=7', false, [], true, false, true, false);</script><script type="text/javascript">var ezouid = "1";</script><base href="https://sendy.daily-dev-tips.com/api/subscribers/active-subscriber-count.php"><script type='text/javascript'> var ezoTemplate = 'old_site_noads'; if(typeof ezouid == 'undefined') { var ezouid = 'none'; } var ezoFormfactor = '1'; var ezo_elements_to_check = Array(); </script> <script data-ezscrex="false" type='text/javascript'> var soc_app_id = '0'; var did = 408517; var ezdomain = 'daily-dev-tips.com'; var ezoicSearchable = 1; </script> <script data-ezscrex="false" type="text/javascript" data-cfasync="false">var _ezaq = {"ad_cache_level":0,"ad_lazyload_version":0,"ad_load_version":0,"city":"The Dalles","country":"US","days_since_last_visit":-1,"domain_id":408517,"engaged_time_visit":0,"ezcache_level":1,"ezcache_skip_code":8,"form_factor_id":1,"framework_id":1,"is_return_visitor":false,"is_sitespeed":0,"last_page_load":"","last_pageview_id":"","lt_cache_level":0,"metro_code":820,"page_ad_positions":"","page_view_count":0,"page_view_id":"2daf0911-6958-4839-7e19-1b83275532ea","position_selection_id":0,"postal_code":"97058","pv_event_count":0,"response_size_orig":4,"response_time_orig":288,"serverid":"34.219.211.72:15785","state":"OR","t_epoch":1669521655,"template_id":120,"time_on_site_visit":0,"url":"https://sendy.daily-dev-tips.com/api/subscribers/active-subscriber-count.php","user_id":0,"word_count":1,"worst_bad_word_level":0};var _ezExtraQueries = "&ez_orig=1";</script> <script data-ezscrex='false' data-pagespeed-no-defer data-cfasync='false'> function create_ezolpl(pvID, rv) { var d = new Date(); d.setTime(d.getTime() + (365*24*60*60*1000)); var expires = "expires="+d.toUTCString(); __ez.ck.setByCat("ezux_lpl_408517=" + new Date().getTime() + "|" + pvID + "|" + rv + "; " + expires, 3); } function attach_ezolpl(pvID, rv) { if (document.readyState === "complete") { create_ezolpl(pvID, rv); } if(window.attachEvent) { window.attachEvent("onload", create_ezolpl, pvID, rv); } else { if(window.onload) { var curronload = window.onload; var newonload = function(evt) { curronload(evt); create_ezolpl(pvID, rv); }; window.onload = newonload; } else { window.onload = create_ezolpl.bind(null, pvID, rv); } } } __ez.queue.addFunc("attach_ezolpl", "attach_ezolpl", ["2daf0911-6958-4839-7e19-1b83275532ea", "false"], false, ['/detroitchicago/boise.js'], true, false, false, false); </script></head><body>1894<script type='text/javascript' style='display:none;' async> __ez.queue.addFile('/detroitchicago/edmonton.webp', '/detroitchicago/edmonton.webp?a=a&cb=0&shcb=34', true, ['/detroitchicago/minneapolis.js'], true, false, false, false); __ez.queue.addFile('/porpoiseant/jellyfish.webp', '/porpoiseant/jellyfish.webp?a=a&cb=0&shcb=34', false, [], true, false, false, false); </script> <script>__ez.queue.addFile('/tardisrocinante/vitals.js', '/tardisrocinante/vitals.js?gcb=0&cb=3', false, ['/detroitchicago/minneapolis.js'], true, false, true, false);</script> <script type="text/javascript" data-cfasync="false"></script> <script>var _audins_dom="daily_dev_tips_com",_audins_did=408517;__ez.queue.addDelayFunc("audins.js","__ez.script.add", "//go.ezoic.net/detroitchicago/audins.js?cb=195-0");</script><noscript><div style="display:none;"><img src="//pixel.quantserve.com/pixel/p-31iz6hfFutd16.gif?labels=Domain.daily_dev_tips_com,DomainId.408517" border="0" height="1" width="1" alt="Quantcast"/></div></noscript> <script>__ez.queue.addFile('/beardeddragon/drake.js', '/beardeddragon/drake.js?gcb=0&cb=4', false, [], true, false, true, false);</script></body></html> devs and subscribe to my newsletter