简体   繁体   中英

How do you use form input to filter objects inside an Observable array?

How do I use the form input above these flags to filter the flags beneath?

https://stackblitz.com/edit/angular-ivy-s2ujmr?file=src/app/country-card/country-card.component.html


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. If this method should work, please explain what I did wrong.


After DAYS of google searching and reading SO posts I came up with

home.component.html

<div>
  <input type="text" [formControl]="myControl" />
  <app-country-card *ngFor="let country of countries$ | async" [country]="country"></app-country-card>
</div>

home.component.ts

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$ = this.myControl.valueChanges.pipe(
      startWith(''),
      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));
  }
}

but this doesn't work either. The page remains blank and no flags are loaded/rendered until something is typed. I need all the results to render, THEN be able to filter them.


I'm new to Angular and this has been so much more difficult than I anticipated. I've spent days trying to make this work and can't afford any more time. I'm desperate for help. Can someone please tell me what I'm doing wrong and how to fix this?

In the first piece of code you create a pipeline (a piped observable), but you do anything with it. It's not returned and you don't subscribe to it. You have to .subscribe() to an Observable , else it won't do anything.

Also, if you feel the need to declare a parameter as any , as you did in this.countries$.pipe(filter((country: any) =>... , you are usually doing something wrong. Please take a look at the docs for filter . It's not filtering elements of an array, as [].filter(...) does and as you use it.

Your second approach is more servicable. Your mistake here is, that you only filter, when the search term changes. You don't filter again if the data is loaded. You need to react to both events: data being loaded and search term changing. You could do this with the combineLatest -operator :

// requests & emits all countries
const loadCountries$ = this.apiService.getAllCountries().pipe(
  tap((countries) => this.allCountries = countries)
);

// emits the search term when the user types, always starts with ''
const searchTerm$ = this.myControl.valueChanges.pipe(
  startWith('')
);

// whenever either loadCountries$ or searchTerm$ emits
// (but only after each emitted at least once)
// applies a filter-function and emits the filtered countries.
this.countries$ = combineLatest(loadCountries$, searchTerm$).pipe(
  map(([countries, searchTerm]) => this.filter(countries, searchTerm)),
);

Note that in this example this.filter was changed to take two arguments, the countries so filter and the search term. You might as well keep your version and use the member this.allCountries .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM