1. Знакомство с [(ngModel)]
Если вы когда-нибудь писали формы на чистом HTML или даже в React, то знаете, как иногда утомительно следить за состоянием каждого поля: нужно писать отдельные обработчики событий, вручную обновлять значения, синхронизировать данные между моделью и представлением. В Angular Template-driven Forms всё становится проще благодаря директиве ngModel.
[(ngModel)] — это директива для двусторонней привязки данных. Она связывает переменную в компоненте и поле формы в шаблоне так, что любые изменения в одном месте мгновенно отражаются в другом. Это как постоянный телепатический канал между вашим компонентом и HTML!
Если бы у вас был такой канал с холодильником, вы бы всегда знали, когда там заканчивается молоко. Но пока Angular не умеет связывать холодильники, он прекрасно работает с формами.
- [(ngModel)] связывает значение поля формы и переменную компонента.
- Любое изменение в input — меняет переменную.
- Любое изменение переменной — меняет input.
Подключение FormsModule
Перед тем как использовать [(ngModel)], убедитесь, что вы подключили модуль FormsModule в вашем приложении. Без этого Angular будет смотреть на ваши скобочки как на странный набор символов.
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; // Вот он, FormsModule!
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, FormsModule], // Добавьте FormsModule сюда!
bootstrap: [AppComponent]
})
export class AppModule {}
2. Синтаксис [(ngModel)]: как это выглядит
Синтаксис двусторонней привязки в Angular — это не просто квадратные или круглые скобки, а их комбо:
<input [(ngModel)]="username">
Пояснение:
- Квадратные скобки [ ] — это привязка значения (property binding).
- Круглые скобки ( ) — это слушатель событий (event binding).
- Вместе они дают "banana in a box" — [( )]. (Да, в англоязычном сообществе это реально называют "банан в коробке" из-за внешнего вида.)
Пример в шаблоне:
<input [(ngModel)]="username" placeholder="Введите имя">
<p>Вы ввели: {{ username }}</p>
В компоненте:
export class AppComponent {
username = '';
}
Каждый раз, когда вы вводите буквы в поле, переменная username автоматически обновляется, и наоборот.
3. Пример: Полная форма с [(ngModel)]
Давайте сделаем простую форму регистрации пользователя с полями "Имя" и "Email". Мы будем использовать [(ngModel)] для каждого поля.
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
name = '';
email = '';
submitForm() {
alert(`Имя: ${this.name}\nEmail: ${this.email}`);
}
}
app.component.html
<form (ngSubmit)="submitForm()">
<label>
Имя:
<input [(ngModel)]="name" name="name" required>
</label>
<br>
<label>
Email:
<input [(ngModel)]="email" name="email" required>
</label>
<br>
<button type="submit">Зарегистрироваться</button>
</form>
<p>Текущее имя: {{ name }}</p>
<p>Текущий email: {{ email }}</p>
Важный момент:
У каждого input, использующего [(ngModel)] внутри формы, должно быть указано свойство name. Без этого Angular будет ворчать и не сможет правильно связать поля с формой.
4. Как работает двусторонняя привязка под капотом?
Когда вы используете [(ngModel)], Angular автоматически:
- Устанавливает значение input из переменной компонента.
- Слушает событие ввода пользователя (input), чтобы обновить переменную.
- Если переменная изменится в компоненте (например, по кнопке), input тоже обновится.
Это работает не только для input, но и для select, textarea и даже radio-кнопок!
Пример с textarea и select
<textarea [(ngModel)]="message" name="message"></textarea>
<select [(ngModel)]="country" name="country">
<option value="ru">Россия</option>
<option value="us">США</option>
<option value="cn">Китай</option>
</select>
5. Полезные нюансы
[(ngModel)] вне формы: можно, но осторожно!
Иногда вам нужно просто связать переменную и input без формы. Это работает, но Angular будет ругаться в консоли, если вы не укажете атрибут name. Для input вне формы можно не указывать name, но если появятся ошибки — просто добавьте его.
<input [(ngModel)]="search" placeholder="Поиск">
Привязка к объектам и массивам
[(ngModel)] можно использовать не только с простыми переменными, но и с полями объектов:
user = {
firstName: '',
lastName: ''
};
<input [(ngModel)]="user.firstName" name="firstName">
<input [(ngModel)]="user.lastName" name="lastName">
Работает и с элементами массива (хотя тут уже нужно быть осторожнее):
tags = ['', '', ''];
<input [(ngModel)]="tags[0]" name="tag1">
<input [(ngModel)]="tags[1]" name="tag2">
<input [(ngModel)]="tags[2]" name="tag3">
[(ngModel)] и валидация
Angular автоматически добавляет CSS-классы и свойства для валидации input-ов с [(ngModel)]:
- ng-valid — значение валидно.
- ng-invalid — невалидно.
- ng-touched — поле было в фокусе.
- ng-untouched — поле не трогали.
Это позволяет легко стилизовать поля в зависимости от их состояния.
Пример:
<input #emailInput="ngModel" [(ngModel)]="email" name="email" required email>
<span *ngIf="emailInput.invalid && emailInput.touched" style="color: red;">
Некорректный email!
</span>
[(ngModel)] и радиокнопки, чекбоксы
Радиокнопки:
<label>
<input type="radio" name="gender" [(ngModel)]="gender" value="male"> Мужчина
</label>
<label>
<input type="radio" name="gender" [(ngModel)]="gender" value="female"> Женщина
</label>
Чекбоксы:
<input type="checkbox" [(ngModel)]="isActive" name="active">
<p>Активен: {{ isActive }}</p>
[(ngModel)] и события: реагируем на изменения
Иногда нужно что-то делать при каждом изменении значения. Можно использовать событие ngModelChange:
<input [(ngModel)]="username" (ngModelChange)="onUsernameChange($event)" name="username">
onUsernameChange(value: string) {
console.log('Новое имя:', value);
}
6. Практика: мини-приложение «Калькулятор»
Давайте расширим наше учебное приложение и добавим простейший калькулятор, используя [(ngModel)] для двусторонней привязки:
app.component.ts
export class AppComponent {
num1 = 0;
num2 = 0;
result = 0;
add() {
this.result = this.num1 + this.num2;
}
}
app.component.html
<input type="number" [(ngModel)]="num1" name="num1">
+
<input type="number" [(ngModel)]="num2" name="num2">
<button (click)="add()">=</button>
<span>{{ result }}</span>
Теперь любые изменения в input сразу попадают в переменные компонента, и при нажатии на кнопку результат вычисляется.
7. Типичные ошибки при использовании [(ngModel)]
Ошибка №1: забыли FormsModule.
Если вы забыли импортировать FormsModule в свой модуль, Angular выдаст ошибку вида:
Can't bind to 'ngModel' since it isn't a known property of 'input'.
Всегда проверяйте, что FormsModule есть в разделе imports.
Ошибка №2: забыли атрибут name в input внутри формы.
Без атрибута name Angular не сможет правильно связать поле с формой, и вы получите ошибку в консоли.
Ошибка №3: используете [(ngModel)] с реактивными формами.
В Reactive Forms [(ngModel)] не нужен и может привести к конфликтам и предупреждениям. Не смешивайте подходы!
Ошибка №4: двусторонняя привязка к несуществующей переменной.
Если вы написали [(ngModel)]="foo.bar" а объекта foo нет — получите ошибку Cannot read property 'bar' of undefined.
Всегда инициализируйте объекты и массивы заранее.
Ошибка №5: забыли, что [(ngModel)] работает только с простыми типами.
Попытка привязать [(ngModel)] к сложному выражению (например, функции или вычисляемому значению) не сработает.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ