Having a hard time showing filtered observable

I’m trying to build something fairly straightforward. I am reading in a json file using an observable. This works. No problem.

I am then trying to allow a search for the content in the list. When I altered the code to make it searchable, nothing works.

If I change the ion-list to look at results, it works and displays the list. If I change it to look at information, it doesn’t work.

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/"></ion-back-button>
    </ion-buttons>
    <ion-title>Hospitals</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-searchbar [(ngModel)]="searchTerm" (ionChange)="searchChanged()"></ion-searchbar>
  <ion-list *ngIf="information">
    <ion-item button *ngFor="let item of (information | async)" routerDirection="forward" [routerLink]="['/hospital-details/'+ item.id]">
      <ion-label text-wrap>
        <h3>{{ item.name }}</h3>
      </ion-label>
    </ion-item>
  </ion-list>

</ion-content>

ts file

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Routes, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { GetHospitalsService } from '../../services/get-hospitals.service';
import { SearchPipe } from './../../search.pipe';
import {filter, map} from 'rxjs/operators';


@Component({
  selector: 'app-hospitals',
  templateUrl: './hospitals.page.html',
  styleUrls: ['./hospitals.page.scss'],
})
export class HospitalsPage implements OnInit {

  // information = null;
  hospitals = null;
  searchTerm = '';

  results: Observable<any>;
  information: Observable<any>;


  constructor(private activatedRoute: ActivatedRoute, private hospitalService: GetHospitalsService, private router: Router) { }
  ngOnInit() {
    console.log('Hospitals enter');

    this.results = this.hospitalService.getHospitals1();
    this.information = this.filterArr2(this.searchTerm);

  }
  filterArr2(search): Observable<any> {
    return this.results.pipe(
          map(oneObj => oneObj.name.toLowerCase().indexOf(search) > -1)
      );
  }

  searchChanged() {
    const mySearch = this.searchTerm.toLowerCase();
    this.information = this.filterArr2(mySearch);
  }


}

Error message

ERROR TypeError: Cannot read property 'toLowerCase' of undefined
    at MapSubscriber.project (hospitals.page.ts:34)

Here is a pic of what I get when I switch it to results.

If I change it to a subscribe, I get a different error, something like:

InvalidPipeArgument: ‘[object Object][object Object][object Object][object Object]’ for pipe ‘AsyncPipe’

Please, can anyone tell me the RIGHT way to do this? I’m banging my head against the desk so much I’m giving myself a concussion.

Try to use (ionInput) instead of ngModel to assign the inputed value to searchTerm.

Your first error, Cannot read property 'toLowerCase' of undefined, displays that searchTerm is null when you try to transform it in toLowerCase

Maybe (not sure):

(ionInput)="something($event)"
something($event) {
        this.searchTerm = $event.target.value;
}

1 Like

Hi

A few things I see or wonder

  • You use map to filter results, but you should use the filter operator
  • I wonder if the search results will get updated as the search action will not trigger the observable to emit a new list which you can filter. For each search change you would need to reget the observable from hospitalService. Try to refactor your code without using async to see if it at all works.
  • I would think the undefined error pops up because an entry in the data does not have a name field. Try console.log in the filter function

Some ideas

Regards

Tom

You will definitely need to pass in the event to your function and can do so using the searchbar control.

You’ll also want to filter after setting information equal to the object that contains the data from your http call.

Updated my code:

html file

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/"></ion-back-button>
    </ion-buttons>
    <ion-title>Hospitals</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-searchbar (ionInput)="searchChanged($event)"></ion-searchbar>
  <ion-list *ngIf="results">
    <ion-item button *ngFor="let item of (information | async)" routerDirection="forward" [routerLink]="['/hospital-details/'+ item.id]">
      <ion-label text-wrap>
        <h3>{{ item.name }}</h3>
      </ion-label>
    </ion-item>
  </ion-list>

</ion-content>

ts file

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Routes, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { GetHospitalsService } from '../../services/get-hospitals.service';
import { SearchPipe } from './../../search.pipe';
import {filter, map} from 'rxjs/operators';


@Component({
  selector: 'app-hospitals',
  templateUrl: './hospitals.page.html',
  styleUrls: ['./hospitals.page.scss'],
})
export class HospitalsPage implements OnInit {

  // information = null;
  hospitals = null;
  searchTerm = '';

  results: Observable<any>;
  information: Observable<any>;


  constructor(private activatedRoute: ActivatedRoute, private hospitalService: GetHospitalsService, private router: Router) { }
  ngOnInit() {
    console.log('Hospitals enter');

    this.results = this.hospitalService.getHospitals1();
    this.information = this.filterArr2(this.searchTerm);

  }
  filterArr2(search): Observable<any> {
    console.log('enter the filterArr2');
    console.log('this.results', this.results);
    return this.hospitalService.getHospitals1().pipe(
          // map(oneObj => oneObj.name.toLowerCase().indexOf(search) > -1)
          map(data => data.filter(data.name.toString().toLowerCase().indexOf(search) > -1))
      );
  }

  searchChanged($event) {
    console.log('search changed entered');
    this.searchTerm = $event.target.value;
    console.log('search term passed', this.searchTerm);
    const mySearch = this.searchTerm.toLowerCase();
    console.log('search lower', mySearch);
    this.information = this.filterArr2(mySearch);
  }


}

I get the following error:

ERROR TypeError: Cannot read property 'toString' of undefined
    at MapSubscriber.project (hospitals.page.ts:37)

So I got it working. Here’s my code in case anyone else is having this issue.

json

{
  "hospitals": [ {
    "id": "1",
    "name": "Lehigh Valley Hospital-Cedar Crest",
    "address": "1200 South Cedar Crest Blvd, Allentown, PA 18105",
    "website": "www.lvhn.org",
    "trauma": "Level I",
    "peds": "Level II",
    "stroke": "Comprehensive",
    "ebola": "No",
    "phone": "610-402-8000",
    "special": "Special Annodote",
    "speciallink": "www.lvhn.org"
  },
    {
      "id": "2",
      "name": "Lehigh Valley Hospital-17th Street",
      "address": "17th and Chew Sts, Allentown, PA 18105",
      "website": "www.lvhn.org",
      "trauma": "N/A",
      "peds": "N/A",
      "stroke": "N/A",
      "ebola": "No",
      "phone": "610-969-2388",
      "special": "Special Annodote",
      "speciallink": "www.lvhn.org"
    },
    {
      "id": "3",
      "name": "Lehigh Valley Hospital-Mulenberg",
      "address": "2545 Schoenersville Rd, Bethlehem, PA 18017",
      "website": "www.lvhn.org",
      "trauma": "N/A",
      "peds": "N/A",
      "stroke": "Primary",
      "ebola": "Yes",
      "phone": "610-402-8000",
      "special": "Special Annodote",
      "speciallink": "www.lvhn.org"
    },
    {
      "id": "4",
      "name": "Lehigh Valley Hospital-Hazleton",
      "address": "700 East Broad St, Hazleton, PA 18201",
      "website": "hazleton.lvhn.org",
      "trauma": "N/A",
      "peds": "N/A",
      "stroke": "Primary",
      "ebola": "No",
      "phone": "570-501-4000",
      "special": "Special Annodote",
      "speciallink": "www.lvhn.org"
    }
  ]
}

html

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/"></ion-back-button>
    </ion-buttons>
    <ion-title>Hospitals</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-searchbar placeholder="Search by Name" [(ngModel)]="searchTerm" (ionChange)="filterMe()"></ion-searchbar>
  <ion-list *ngIf="results">
    <ion-item button *ngFor="let item of (results | async)" routerDirection="forward" [routerLink]="['/hospital-details/'+ item.id]">
      <ion-label text-wrap>
        <h3>{{ item.name }}</h3>
      </ion-label>
    </ion-item>
  </ion-list>

</ion-content>

ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Routes, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { GetHospitalsService } from '../../services/get-hospitals.service';
import { SearchPipe } from './../../search.pipe';
import {filter, map, switchMap} from 'rxjs/operators';
import { FormControl } from '@angular/forms';
import {json} from '@angular-devkit/core';


@Component({
  selector: 'app-hospitals',
  templateUrl: './hospitals.page.html',
  styleUrls: ['./hospitals.page.scss'],
})
export class HospitalsPage implements OnInit {

  // information = null;
  searchTerm = '';
  searchText = '';

  data: any;
  results: Observable<any[]>;
  information: Observable<any[]>;

  myControl = new FormControl();

  constructor(private activatedRoute: ActivatedRoute, private hospitalService: GetHospitalsService, private router: Router) { }
  ngOnInit() {
    console.log('Hospitals enter');

    this.results = this.hospitalService.getHospitals1();
    this.information = this.results;
  }

  filterMe() {
    console.log('searchterm', this.searchTerm);
    this.searchText = '';
    if (this.searchTerm != null) {
      this.searchText = this.searchTerm.toLowerCase();
    } else {
      this.searchText = '';
    }

    this.results = this.information.pipe(
        map((reports: any[]) => reports.filter(p => {
          if (p.name.toString().toLowerCase().indexOf(this.searchText) > -1) {return p; }
        }))
    );

    console.log('results - after', this.results);

  }

}
1 Like