Как работает двустороннее связывание [(ngModel)]?angular-17

Основной механизм

Двустороннее связывание [(ngModel)] — это синтаксический сахар Angular, который сочетает:

  1. Привязку свойства ([ngModel]) - данные из компонента в шаблон
  2. Привязку события ((ngModelChange)) - данные из шаблона в компонент

Эквивалентная запись:

<input [ngModel]="value" (ngModelChange)="value = $event">

Сокращенная запись:

<input [(ngModel)]="value">

Требования для использования

  1. Импорт FormsModule (для шаблонных форм):
import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [FormsModule]
})
  1. Импорт ReactiveFormsModule (для реактивных форм, если используется вместе)

Под капотом: как это работает

  1. Директива NgModel реализует интерфейс ControlValueAccessor:
interface ControlValueAccessor {
  writeValue(obj: any): void;    // Компонент -> DOM
  registerOnChange(fn: any): void; // DOM -> Компонент
  registerOnTouched(fn: any): void;
}
  1. Процесс обновления:
    • При изменении в компоненте вызывается writeValue()
    • При изменении в UI (например, ввод текста) вызывается зарегистрированная функция onChange

Пример кастомной реализации

Создание собственного элемента с двусторонним связыванием:

@Component({
  selector: 'app-custom-input',
  template: `
    <input [value]="value" (input)="onInput($event)">
  `,
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: CustomInputComponent,
    multi: true
  }]
})
export class CustomInputComponent implements ControlValueAccessor {
  value: string;
  onChange: (value: string) => void;
  onTouched: () => void;

  writeValue(value: string): void {
    this.value = value;
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  onInput(event: Event) {
    this.value = (event.target as HTMLInputElement).value;
    this.onChange(this.value);
  }
}

Использование:

<app-custom-input [(ngModel)]="data"></app-custom-input>

Особенности работы

  1. Change Detection:

    • При каждом изменении значения запускается механизм обнаружения изменений
    • Можно оптимизировать с помощью OnPush стратегии
  2. Валидация:

    • NgModel автоматически работает с валидаторами
    • Добавление стандартных валидаторов:
<input [(ngModel)]="email" required email>
  1. Отличия от реактивных форм:
    • [(ngModel)] используется в шаблонных формах
    • Для реактивных форм предпочтительнее formControlName

Распространенные проблемы

  1. Использование без FormsModule:

    • Ошибка: "Can't bind to 'ngModel' since it isn't a known property"
  2. Конфликт имен:

    • Если есть input с @Input() ngModel, будет конфликт
  3. Производительность:

    • Частые обновления могут снижать производительность
    • Решение: debounceTime или ручное управление

Резюмируем

  1. [(ngModel)] — это комбинация property и event binding
  2. Требует импорта FormsModule
  3. Работает через:
    • Директиву NgModel
    • Интерфейс ControlValueAccessor
  4. Подходит для:
    • Простых форм
    • Быстрого прототипирования
  5. Альтернативы:
    • Реактивные формы для сложных сценариев
    • Кастомные реализации ControlValueAccessor

Понимание механизма [(ngModel)] необходимо для:

  • Эффективной работы с формами в Angular
  • Создания кастомных элементов с двусторонним связыванием
  • Оптимизации производительности форм