As is, we submit a request to the server with every keystroke. This is not a desirable behavior, because it can lead to a bad user experience, drain battery life, result in wasted network requests, and create performance issues both on the client and server side. Users make typos; they can change their mind about what they are inputting and rarely ever, the first few characters of information input result in useful results.
We can still listen to every keystroke, but we don't have to react to every keystroke. By leveraging throttle/debounce, we can limit the number of events generated to a predetermined interval and still maintain the type-as-you-search functionality.
It is very easy to inject throttling into the observable stream using RxJS/debounceTime.
Implement debounceTime with pipe:
src/app/city-search/city-search.component.ts
import { debounceTime } from 'rxjs/operators'
this.search.valueChanges
.pipe(debounceTime(1000))
.subscribe(...)
debounceTime will, at a maximum, run a search every second, but also run a last search after the user has stopped typing. In comparison, RxJS/throttleTime will only run a search every second, on the second, and will not necessarily capture the last few characters the user may have input.
RxJS also has the throttle and debounce functions, which you can use to implement custom logic to limit input that is not necessarily time-based.
Since this is a time- and event-driven functionality, break point debugging is not feasible. You may monitor the network calls within the Chrome Dev Tools | Network tab, but to get a more real-time feeling of how often your search handler is actually being invoked, add a console.log statement.