ค้นหาเว็บไซต์

javascript - การใช้ RxJS fromEvent และ @ViewChild เทียบกับเหตุการณ์ keyup และ RxJS Subject สำหรับการเปลี่ยนแปลงค่าของช่องกรอกข้อมูล


ฉันมีคอมโพเนนต์ง่าย ๆ ที่ทำการค้นหาด้วยค่าที่พิมพ์ลงไปในกล่องค้นหา ฉันพบว่ามีสองวิธีที่แตกต่างกันในการเขียนโค้ดนี้และทั้งสองวิธีก็ทำงานได้

วิธีที่ 1 ใช้ fromEvent จาก RxJS และตั้งค่า observable เพื่อจับการเปลี่ยนแปลงค่าของช่องป้อนข้อมูลโดยใช้การอ้างอิง @ViewChild ไปยังองค์ประกอบ input

วิธีที่ 2 จะจับการเปลี่ยนแปลงค่าของช่องป้อนข้อมูลโดยใช้เหตุการณ์ keyup และส่งค่าที่ใหม่ไปยัง Subject ของ RxJS

ตัวอย่างส่วนใหญ่ที่ฉันพบใน Stackoverflow และเว็บไซต์อื่น ๆ ใช้ @ViewChild สำหรับสถานการณ์ประเภทนี้ มีเหตุผลใดที่จะใช้วิธีใดวิธีหนึ่งมากกว่าวิธีอื่น หรือเป็นเรื่องของความชอบส่วนบุคคล?

วิธีที่ 1

app.component.html

<input type="text" #search placeholder="Search" />
<li *ngFor="let state of states$ | async">
  {{ state.text }}
</li>

แอป.คอมโพเนนต์.ทีเอส

export class AppComponent implements OnInit {
  @ViewChild('search', { static: true }) search: ElementRef;
  states$: Observable<any[]>;

  constructor(private dataService: DataService) {}

  ngOnInit(): void {
    this.states$ = fromEvent(this.search.nativeElement, 'input').pipe(
      map((event: any) => event.target.value),
      debounceTime(500),
      distinctUntilChanged(),
      switchMap((searchText: string) => this.dataService.getData(searchText))
    );
  }
}

วิธีที่ 2

app.component.html

<input
  type="text"
  (keyup)="searchStringChange(getValue($event))"
  placeholder="Search"
/>
<li *ngFor="let state of states$ | async">
  {{ state.text }}
</li>

แอป.คอมโพเนนต์.ทีเอส

export class AppComponent implements OnInit {
  states$: Observable<any[]>;
  private searchText$ = new Subject<string>();

  constructor(private dataService: DataService) {}

  ngOnInit(): void {
    this.states$ = this.searchText$.pipe(
      debounceTime(500),
      distinctUntilChanged(),
      tap({ next: (searchText: string) => console.log(searchText) }),
      switchMap((searchText: string) => this.dataService.getData(searchText))
    );
  }

  searchStringChange(userInput: string) {
    this.searchText$.next(userInput);
  }

  getValue(event: Event): string {
    return (event.target as HTMLInputElement).value;
  }
}

คำตอบ #1

ฉันพยายามหลีกเลี่ยงการใช้ @ViewChild เท่าที่จะทำได้ เหตุผลคือโค้ดจะง่ายขึ้นเมื่อคอมโพเนนต์ไม่จำเป็นต้องจัดการกับปัญหาเกี่ยวกับเทมเพลต เมื่อคุณนำปัญหาเกี่ยวกับเทมเพลตเข้ามา คุณต้องกังวลว่าธาตุที่ต้องการจะอยู่ใน DOM หรือไม่ (มันอาจถูกซ่อนโดย ngIf) และคุณต้องกังวลเกี่ยวกับการเข้าถึงมันในฮุคของไลฟ์ไซเคิลของแองกูลาร์ที่เหมาะสม

เมื่อใช้ 'แนวทางแบบ subject' ส่วนประกอบจะต้องสนใจเพียงข้อมูล และแม่แบบสามารถส่งข้อมูลตามเหตุการณ์ที่เกี่ยวข้องได้อย่างง่ายดาย

หมายเหตุ ใน "วิธีที่ 2" ของคุณไม่จำเป็นต้องใช้ ngOnInit คุณสามารถเริ่มต้น observable ของคุณได้ง่ายๆ "บนคลาส"

export class AppComponent {

  private searchText$ = new Subject<string>();

  states$: Observable<any[]> = this.searchText$.pipe(
    debounceTime(500),
    distinctUntilChanged(),
    tap(searchText => console.log(searchText)),
    switchMap(searchText => this.dataService.getData(searchText))
  ); 

  constructor(private dataService: DataService) {}

  searchStringChange(userInput: string) {
    this.searchText$.next(userInput);
  }

  getValue(event: Event): string {
    return (event.target as HTMLInputElement).value;
  }
}

ยังมีวิธีที่สามในการจัดการเรื่องนี้ เนื่องจากคุณกำลังจัดการกับแบบฟอร์มอินพุต คุณสามารถใช้ Reactive Forms ของ Angular วัตถุ FormControl มี observable valueChanges ที่สะดวก ซึ่งจะถูกส่งออกทุกครั้งที่ค่าของอินพุตเปลี่ยนแปลง:

export class AppComponent {

  searchText = new FormControl<string>('');

  states$ = this.searchText.valueChanges.pipe(
    debounceTime(500),
    distinctUntilChanged(),
    tap(searchText => console.log(searchText)),
    switchMap(searchText => this.dataService.getData(searchText))
  ); 

  constructor(private dataService: DataService) {}

}
<input type="text" [formControl]="searchText" placeholder="Search" />

<li *ngFor="let state of states$ | async">
  {{ state.text }}
</li>

นี่คือตัวอย่างการทำงานของ StackBlitz