Перейти к содержанию

Модуль FormsModule и директива NgModel

Для взаимодействия с пользователем в веб-приложениях, как правило применяются формы. В Angular прежде чем использовать формы в компонентах, нам надо импортировать в главном модуле AppModule модуль FormsModule, который позволяет работать с формами:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

import { FormsModule } from '@angular/forms';

@NgModule({
    imports: [BrowserModule, FormsModule],
    declarations: [AppComponent],
    bootstrap: [AppComponent],
})
export class AppModule {}

Кроме того, в файле конфигурации приложения package.json среди списка используемых зависимостей должен быть указан пакет @angular/forms:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
    "name": "helloapp",
    "version": "1.0.0",
    "description": "First Angular 7 Project",
    "author": "Eugene Popov <metanit.com>",
    "scripts": {
        "dev": "webpack-dev-server --hot --open",
        "build": "webpack"
    },
    "dependencies": {
        "@angular/forms": "~7.0.0"
        // остальные пакеты
    },
    "devDependencies": {
        // остальные пакеты
    }
}

При работе с формами ключевым моментом является использование директивы NgModel. Эта директива с помощью переданной модели создает объект FormControl и привязывает эту модель к созданному элементу формы. Объект FormControl отслеживает значение модели, а также отвечает за валидацию этого значения и взаимодействие с пользователем.

Данная директива принимает переданную ей модель в качестве входного свойства. Причем мы можем использовать как однонаправленную, так и двунаправленную привязку.

Если нам надо просто вывести значение модели в поле ввода, то можно ограничиться и однонаправленной привязкой:

1
<input name="title" [ngModel]="title" />

Это обычная привязка свойства, где в качестве модели используется некоторое свойство title, определенное в классе компонента.

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

1
<input name="title" [(ngModel)]="title" />

Рассмотрим применение NgModel на примере. Возьмем проект с базовой структурой:

Структура проекта

Определим в файле app.component.ts следующий компонент:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import { Component } from '@angular/core';

export class Phone {
    constructor(
        public title: string,
        public price: number,
        public company: string
    ) {}
}

@Component({
    selector: 'my-app',
    template: `
        <div class="col-xs-8">
            <div class="form-group">
                <label>Название модели</label>
                <input
                    class="form-control"
                    name="title"
                    [(ngModel)]="title"
                />
            </div>
            <div class="form-group">
                <label>Цена</label>
                <input
                    type="number"
                    class="form-control"
                    name="price"
                    [(ngModel)]="price"
                />
            </div>
            <div class="form-group">
                <label>Производитель</label>
                <select
                    class="form-control"
                    name="company"
                    [(ngModel)]="company"
                >
                    <option
                        *ngFor="let comp of companies"
                        [value]="comp"
                    >
                        {{ comp }}
                    </option>
                </select>
            </div>
            <div class="form-group">
                <button
                    class="btn btn-default"
                    (click)="
                        addPhone(title, price, company)
                    "
                >
                    Добавить
                </button>
            </div>
        </div>
        <div>
            <h3>Добавленные элементы</h3>
            <ul *ngFor="let p of phones">
                <li>
                    {{ p.title }} ({{ p.company }}) -
                    {{ p.price }}
                </li>
            </ul>
        </div>
    `,
})
export class AppComponent {
    phones: Phone[] = [];
    companies: string[] = [
        'Apple',
        'Huawei',
        'Xiaomi',
        'Samsung',
        'LG',
        'Motorola',
        'Alcatel',
    ];

    addPhone(
        title: string,
        price: number,
        company: string
    ) {
        this.phones.push(new Phone(title, price, company));
    }
}

Для представления данных здесь определен класс Phone, в котором есть три свойства. Класс компонента содержит массив объектов Phone. С помощью метода addPhone() в этот массив добавляется новый объект.

Для добавления данных в шаблоне определены три поля ввода. В каждом поле определены директивы типа [(ngModel)]="title". Тем самым фактически определяются некоторые значения, которые привязаны к этим полям. В обработчике нажатия кнопки вызывается метод addPhone(), в который передаются эти значения.

В конце шаблона добавленные данные из массива phones выводятся на страницу:

Скриншот проекта

Все три поля привязаны к отдельным значениям, которые существуют сами по себе. Но мы можем пойти дальше и определить для формы ввода отдельную модель, которая будет инкапсулировать эти значения:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import { Component } from '@angular/core';

export class Phone {
    constructor(
        public title: string,
        public price: number,
        public company: string
    ) {}
}

@Component({
    selector: 'my-app',
    template: `
        <div class="col-xs-10">
            <div class="form-group">
                <label>Название модели</label>
                <input
                    class="form-control"
                    name="title"
                    [(ngModel)]="phone.title"
                />
            </div>
            <div class="form-group">
                <label>Цена</label>
                <input
                    type="number"
                    class="form-control"
                    name="price"
                    [(ngModel)]="phone.price"
                />
            </div>
            <div class="form-group">
                <label>Производитель</label>
                <select
                    class="form-control"
                    name="company"
                    [(ngModel)]="phone.company"
                >
                    <option
                        *ngFor="let comp of companies"
                        [value]="comp"
                    >
                        {{ comp }}
                    </option>
                </select>
            </div>
            <div class="form-group">
                <button
                    class="btn btn-default"
                    (click)="addPhone()"
                >
                    Добавить
                </button>
            </div>
        </div>
        <div>
            <h3>Добавленные элементы</h3>
            <ul *ngFor="let p of phones">
                <li>
                    {{ p.title }} ({{ p.company }}) -
                    {{ p.price }}
                </li>
            </ul>
        </div>
    `,
})
export class AppComponent {
    phone: Phone = new Phone('', 0, 'Huawei');
    phones: Phone[] = [];
    companies: string[] = [
        'Apple',
        'Huawei',
        'Xiaomi',
        'Samsung',
        'LG',
        'Motorola',
        'Alcatel',
    ];

    addPhone() {
        this.phones.push(
            new Phone(
                this.phone.title,
                this.phone.price,
                this.phone.company
            )
        );
    }
}

Для полей ввода здесь создана отдельная переменная phone, к свойствам которой привязаны поля ввода. Стоит также обратить внимание на то, как добавляется новый объект в массив phones — здесь не добавляется напрямую переменная phone, а создается отдельный объект, который инициализируется значениями из переменной phone. А в остальном результат будет тем же, что и в предыдущем примере.

Комментарии