Как реализовать обработку ввода пользователя в поисковую строку в Angular с использованием реактивных форм
Что проверяют
Умеете ли вы превратить поток нажатий клавиш в разумное число запросов к серверу и не показать устаревшую выдачу. Следом почти всегда спрашивают, чем switchMap отличается от mergeMap и concatMap и как вы отписываетесь.
Как отвечать
Для одной строки поиска хватает FormControl, группа не нужна. С типизированными формами пишите new FormControl('', { nonNullable: true }), тогда значение имеет тип string, а не string | null. В шаблоне [formControl]="query", а в standalone-компоненте ReactiveFormsModule подключается в imports самого компонента.
Дальше каркас на valueChanges и операторах RxJS:
debounceTime(300): запрос уходит, когда пользователь перестал печатать, а не на каждый символ;map(q => q.trim())иdistinctUntilChanged(): не шлём повтор, если текст фактически не изменился;filter(q => q.length >= 2): не ищем по одной букве, а пустую строку обрабатываем отдельно и сбрасываем выдачу;switchMap: новый запрос отменяет предыдущий HTTP-запрос. Ответ на «ang» не придёт позже ответа на «angular» и не перетрёт его;catchErrorвнутриswitchMap: ошибка одного запроса превращается в пустой результат или сообщение, а поток поиска продолжает жить.
Результат выводите через async pipe или toSignal(), тогда подписка снимется сама. Если подписываетесь вручную, добавьте takeUntilDestroyed() из @angular/core/rxjs-interop. Состояние загрузки удобно держать отдельным сигналом.
В тестах проверяйте поведение, а не то, что значение записалось в контрол: прокрутите время (fakeAsync и tick(300) при zone.js или фейковые таймеры раннера) и убедитесь через HttpTestingController, что ушёл ровно один запрос с последним текстом.
Где спотыкаются
Ставят mergeMap или subscribe внутри subscribe. Ответы приходят в произвольном порядке, и на экране оказывается выдача по старому запросу.
Ставят catchError снаружи switchMap. Первая сетевая ошибка завершает весь поток, и поиск молча перестаёт работать до перезагрузки страницы.
Не обрабатывают очистку поля. filter отбрасывает пустую строку, запрос не уходит, и под пустым полем висит выдача по прошлому запросу.