typescript 如何使用表单输入过滤可观察项?

zbq4xfa0  于 2023-01-06  发布在  TypeScript
关注(0)|答案(2)|浏览(150)

我有一个包含"countries $"变量的组件:

countries$!: Observable<Country[]>;

我用this data填充到一个"ngOnInit"中,如下所示:

ngOnInit(){
    this.countries$ = this.apiService.getAllCountries();
  }

我在html模板中访问这个变量/Observable,如下所示:

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

我想包括一个搜索栏,过滤器的国家,无论是键入。
我想我可以像这样在管道中使用filter函数:

searchFilterCountries(searchTerm: string){
    this.countries$.pipe(filter((country: any) => country.name.common.toLowerCase().includes(searchTerm.toLowerCase())))
  }

并将输入放入html模板中,如下所示:

<input type="text" class="form-control" (input)="searchFilterCountries($event.target.value)"/>

以便每次有输入时过滤器函数都会触发,从而缩小显示的国家列表。
但是这不起作用。我得到打字错误:
对象可能为"null"。ngtsc(2531)
类型"EventTarget"上不存在属性"value"。ngtsc(2339)
然后,我发现了一个"样本"的工作过滤列表在这里的材料UI
https://material.angular.io/components/autocomplete/examples(过滤器)
我试图实现这一点,并提出了以下代码:

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
      }
    
    }

但是它不起作用,我想是因为这些值是可观察的,而不是可观察的数据。
在"option. name. common"中的"name"属性下,我有几条弯弯曲曲的线显示了一个TS错误,内容如下:

    • 选项.名称.常见TS错误**

类型"Country []"上不存在属性"name"
但如果我这样做:

option => option[0].name.common.toLowerCase().includes(filterValue)))

错误就会消失,但是如果我这样做的话,我就不能搜索所有的值了。
我的思路对吗?我使用的操作符对吗?我如何修复TS错误?我是Angular 操作的新手,不知道所有可用的操作符。如果我使用mergeMap/switchMap,会解决我的问题吗?如果我修复了打字错误,它甚至会工作吗?或者我的方法是错误的吗?
有人能帮我把这个修好吗?

ppcbkaq5

ppcbkaq51#

我想对您当前的代码进行扩展,并建议进行如下更改:

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)
    );
  }
}

因此,我们将原始的国家列表保存在一个单独的变量中,并使用表单控件的valueChange事件来筛选需要显示的国家。
模板应如下所示:

<input type="text" [formControl]="myControl" />

<div *ngFor="let country of countries$ | async">
  <div>Name: {{ country.name.common }}</div>>
</div>
jgwigjjp

jgwigjjp2#

管道示例

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'
  }])
}

诚然,我在这里做了一些“肮脏”的事情,过滤传入的国家数组流可能会更有效一些。还要注意,您仍然需要扩展filter函数来检查所有属性(可以使用for(prop in obj)类型循环来迭代所有属性,以查看是否有任何属性匹配searchText,或者根据需要调整条件。
这个完整的示例显示了过滤器部分,其中不同类型的属性的过滤方式略有不同:
过滤器列表.pipe.ts(可选)

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
    });
  }
}

相关问题