Не сходятся FormControl и Input? Решаем проблему Angular Material

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Знакомая боль каждого Angular-разработчика

Вы когда-нибудь тратили час на поиск ошибки, которая пряталась буквально в одной строчке кода? Если вы работаете с Angular Material Autocomplete и столкнулись с сообщением Can't bind to 'formControl' since it isn't a known property of 'input' — вы не одиноки. Этот вопрос на Stack Overflow собрал тысячи просмотров, и не зря. Ошибка вылетает в самый неожиданный момент, когда кажется, что всё уже работает идеально.

В этой статье мы разберём причины возникновения ошибки, детально сравним три подхода к её исправлению и покажем рабочие примеры на TypeScript. После прочтения вы сможете не только починить автокомплит, но и глубже понять архитектуру Angular. Поехали!

Почему возникает ошибка Can't bind to 'formControl'?

Давайте сразу к делу. Ошибка появляется, когда Angular пытается привязать директиву formControl к элементу <input>, но не находит её в текущем модуле. Звучит логично, но дьявол кроется в деталях.

На самом деле, formControl — это селектор директивы, которая является частью модуля ReactiveFormsModule. Если вы забыли импортировать этот модуль или импортировали его неправильно, Angular просто не знает, что делать с атрибутом [formControl] на вашем инпуте.

Вот типичная ситуация: вы создаёте новый компонент с автокомплитом, добавляете FormControl в TypeScript код, а в шаблоне пишете:

<input type="text" [formControl]="myControl" [matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete">
  <mat-option *ngFor="let option of filteredOptions | async" [value]="option">
    {{option}}
  </mat-option>
</mat-autocomplete>

И тут — бам! Красная ошибка в консоли. Знакомо? Тогда читаем дальше.

Подход 1: Импорт ReactiveFormsModule (Самый простой и правильный)

Первый и самый очевидный способ — убедиться, что ReactiveFormsModule импортирован в нужный модуль. Для standalone-компонентов (Angular 14+) или классических модулей процедура немного отличается.

Для классических модулей (NgModule)

Откройте файл модуля, где зарегистрирован ваш компонент (например, app.module.ts или shared.module.ts), и добавьте импорт:

import { ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatInputModule } from '@angular/material/input';

@NgModule({
  imports: [
    ReactiveFormsModule,
    MatAutocompleteModule,
    MatInputModule,
    // другие импорты
  ],
  // ...
})
export class AppModule { }

Важно: Без MatInputModule директива matInput тоже не сработает, и вы получите похожую ошибку. Импортируйте все три модуля вместе.

Для standalone-компонентов (Angular 14+ и Angular 17)

Если вы используете новую архитектуру, импорт делается прямо в компоненте:

import { Component } from '@angular/core';
import { ReactiveFormsModule, FormControl } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatInputModule } from '@angular/material/input';
import { AsyncPipe } from '@angular/common';

@Component({
  selector: 'app-search',
  standalone: true,
  imports: [
    ReactiveFormsModule,
    MatAutocompleteModule,
    MatInputModule,
    AsyncPipe
  ],
  template: `
    <input type="text" [formControl]="myControl" [matAutocomplete]="auto">
    <mat-autocomplete #auto="matAutocomplete">
      <mat-option *ngFor="let option of filteredOptions | async" [value]="option">
        {{option}}
      </mat-option>
    </mat-autocomplete>
  `
})
export class SearchComponent {
  myControl = new FormControl('');
  options: string[] = ['Python', 'TypeScript', 'Angular', 'React'];
  filteredOptions = this.myControl.valueChanges.pipe(
    // фильтрация
  );
}

Этот подход — золотой стандарт. Он решает проблему в 99% случаев. Но что если ошибка всё ещё появляется?

Подход 2: Проверка версии Angular и совместимости пакетов

Иногда разработчики устанавливают Angular Material версии, несовместимой с текущей версией Angular. Например, вы используете Angular 15, а Material поставили 16-й версии. Это частая причина неожиданных ошибок, включая нашу.

Проверьте файл package.json:

"@angular/core": "^17.0.0",
"@angular/forms": "^17.0.0",
"@angular/material": "^17.0.0"

Все основные пакеты должны быть одной мажорной версии. Если у вас разнобой, выполните команду:

ng update @angular/material @angular/cdk

Это обновит Material до совместимой версии. После обновления перезапустите сервер разработки (ng serve).

Бонусный совет: Если вы используете Angular 17, обратите внимание на новый синтаксис @for вместо *ngFor. Хотя старый синтаксис всё ещё работает, новый может быть предпочтительнее.

Подход 3: Использование FormControlDirective и ручная регистрация

Этот метод — для экстремальных случаев, когда обычные импорты не помогают (например, из-за конфликтов ленивой загрузки модулей). Вы можете вручную импортировать директиву FormControlDirective и добавить её в declarations или imports вашего модуля.

В app.module.ts:

import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormControlDirective } from '@angular/forms';

@NgModule({
  imports: [
    FormsModule,
    ReactiveFormsModule,
    // ...
  ],
  // Иногда требуется явно задекларировать
  // Но в 99% случаев это избыточно
})
export class AppModule { }

На практике этот подход почти не используется, но знание о нём может спасти вас в legacy-проектах с запутанной модульной структурой.

Сравнение подходов: что выбрать?

Давайте сведём всё в наглядную таблицу:

  • Подход 1 (ReactiveFormsModule) — универсальное решение. Подходит для 95% случаев. Просто, надёжно, рекомендуется официальной документацией.
  • Подход 2 (Проверка версий) — обязателен, если первый не сработал. Исключает конфликты пакетов.
  • Подход 3 (FormControlDirective) — резервный вариант для сложных архитектур. Используйте только если первые два не помогли.

Блоги

Рекомендаций по книгам