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
