
Готовы сегодня создать что-нибудь интересное? Я тоже!
На данный момент я тружусь в роли Angular-разработчика, создавая для сотрудников нашей компании инструменты, которые помогают им более эффективно и комфортно решать насущные задачи. Работать с Angular я начала только в мае 2020, но ввиду своего глубокого увлечения фронтенд-разработкой планирую освоить этот навык профессионально.
Я глубоко убеждена, что единственный способ научиться чему-либо — это начать заниматься этим на практике. Только методом смелых проб и ошибок можно шаг за шагом выстроить пирамиду опыта и вырасти из новичка в рок-звезду.
“Обучение — это активный процесс, и учимся мы путем активной деятельности. Как итог — в виде устойчивых навыков оседают только те знания, которые применяются на практике”, — Дэйл Карнеги.
Что будем изучать?
Мы создадим базовую версию приложения для отслеживания привычек, где будут содержаться соответствующие записи, которые можно будет добавлять, редактировать и удалять. Это будет пошаговая инструкция, в которой мы поочередно соберем все компоненты UI и бэкенд, чтобы вы могли наглядно проследить процесс их объединения воедино.

Здесь мы:
- настроим приложение Angular с нуля — без стартовых файлов, полностью разобрав весь процесс;
- установим Angular Material и используем ряд его компонентов для создания красивого UI;
- включим в шаблон UI некоторые из структурных директив Angular для показа и скрытия элементов на основе состояния компонента;
- реализуем простую реактивную форму для получения входных данных.
В конце урока у вас будет рабочее приложение, управляющее списком привычек, которые можно добавлять, редактировать и удалять. Поехали!
Для тех, кто не хочет читать: демо-версия приложения доступна на StackBlitz.
Базовая настройка
Если вы ранее не создавали приложение Angular на своей машине, то убедитесь, что у вас установлены Node.js и Angular CLI.
Откройте предпочтительный для вас терминал. Я в данном уроке буду использовать его встроенный в VSCode вариант.
Перейдите в каталог, где хотите создать приложение и введите следующую команду:

Вам будет задано два вопроса:
- Would you like to add Angular routing? (Добавить маршрутизацию Angular?) — для подтверждения введите y и нажмите ввод.
- Which stylesheet format would you like to use? (Какой формат таблицы стилей использовать?) — выберите предпочтительный с помощью клавиш стрелок. Мне нравится вариант SCSS.
После генерации этих пакетов можно переходить к настройке Angular Material (AM). Для начала перейдите в каталог приложения командой cd и добавьте эту библиотеку в проект:

Вам также понадобится ответить на ряд вопросов:
- Choose the Deep Purple/Amber pre-built theme (Выберите тему Deep Purple/Amber).
- Set up global Angular Material typography styles? (Настроить глобальные стили оформления Angular Material?) — для подтверждения введите y и нажмите ввод.
- Set up browser animations for Angular Material? (Настроить анимации браузера для Angular Material?) — для подтверждения введите y и нажмите ввод.
После этого введите команду “ng serve”, чтобы увидеть весь сгенерированный Angular код.

Пустяки, не так ли? А теперь пора перейти к собственному написанию кода.
Создание элементов UI
Создание нашего удобного красивого UI мы начнем с добавления пары компонентов Angular Material.
Toolbar
В файле app.component.html
удалите весь сгенерированный код и добавьте:
<div class="toolbar-container"> <mat-toolbar class="toolbar" color="primary"> <mat-icon aria-hidden="false" aria-label="check mark icon">fact_check</mat-icon> <h1>Habit Tracker</h1> </mat-toolbar> </div>
Так как для создания элементов mat-toolbar
и mat-icon
мы используем AM, вам также понадобится добавить в файл app.module.ts
следующее:
//... другие импорты... import { MatIconModule } from '@angular/material/icon'; import { MatToolbarModule } from '@angular/material/toolbar'; @NgModule({ declarations: [ AppComponent ], imports: [ //...другие импорты... MatIconModule, MatToolbarModule, ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Примечание: я буду опускать некоторые импорты, чтобы сократить свои вставки с GitHub. Весь код вы сможете найти в конце урока.
Затем добавьте в app.component.scss
следующее:
.toolbar h1 { padding-left: 5px; }
Далее перейдите в styles.scss
и добавьте в стили background-color: #f5f0fe;
для элемента body
.
Сохраните все и полюбуйтесь, какую крутую панель инструментов мы создали.
Если изменений не произошло, то стоит попробовать остановить выполнение приложения, введя CTRL+С в терминале, и заново выполнить ng serve
. Иногда вносимые в app.module.ts
изменения требуют перезапуска.
Использование иконок Angular Material
Среди прочих крутых возможностей AM можно выделить бесплатный функционал иконок. По этому поводу я хочу пояснить несколько моментов:
- отображаемое имя иконки указывается текстом между тегами
<mat-icon>
; - все доступные варианты иконок можно найти на странице Material Design icons;
- По умолчанию иконка в элементе будет наследовать цвет шрифта родительского элемента. Вы же можете использовать один из цветов темы, добавив атрибут цвета. Например,
<mat-icon color="primary">
окрасит иконку в основной цвет темы.

Создание списка
А теперь мы обратимся за помощью к TypeScript, который позволит настроить в шаблоне отображение дат. В этом руководстве не затрагиваются темы локального сохранения информации, поэтому пока что для экспериментирования мы настроим временный список.
Нам нужна модель, сообщающая приложению описание каждой привычки (habit). В директории проекта создайте каталог models
, в нем файл habit.ts
, и в него добавьте:
export class Habit { name: string; frequency: string; description: string; }
Вернитесь к app.component.ts
и добавьте с помощью нашего нового типа свойство массива:
import { Component } from '@angular/core'; import { Habit } from './models/habit'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) export class AppComponent { public habits: Habit[] = [ <Habit>{ name: '15 Minute Walk', frequency: 'Daily', description: 'This habit makes my kitchen look nice and makes my day better the next morning.', }, <Habit>{ name: 'Weed the Garden', frequency: 'Weekly', description: 'The weeds get so out of hand if they wait any longer, and I like how nice our home looks with a clean lawn.', }, ]; }
Не забудьте импортировать тип Habit
.
В app.component.html
под панелью инструментов вставьте этот HTML:
<div class="all-habits"> <h1>All Habits</h1> <div *ngFor="let habit of habits"> <mat-card> <mat-card-title> <mat-icon class="habit-icon" color="accent" aria-hidden="false" aria-label="circle check mark icon" >check_circle_outline</mat-icon > {{ habit.name }} </mat-card-title> <div class="detail-options"> <mat-icon class="habit-icon" color="primary" >edit</mat-icon > <mat-icon class="habit-icon" color="warn" >remove_circle</mat-icon > </div> <mat-card-content> <p> <span class="detail-label">Frequency:</span> {{ habit.frequency }} </p> <p> <span class="detail-label">Why is this habit important to me?</span> <br />{{ habit.description }} </p> </mat-card-content> </mat-card> </div> </div>
Теперь из-за добавления этих новых компонентов AM в шаблон нам будет не хватать несколько импортов. Чтобы это исправить, импортируйте MatCardModule в app.module.ts
:
//...другие импорты... import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; import { MatToolbarModule } from '@angular/material/toolbar'; @NgModule({ declarations: [AppComponent], imports: [ //...другие импорты... MatCardModule, MatIconModule, MatToolbarModule, ], providers: [], bootstrap: [AppComponent], }) export class AppModule {}
Внесите в app.component.scss
следующие классы:
.all-habits { text-align: center; } .all-habits h1 { margin-top: 1em; } .all-habits mat-card { width: 80%; margin: 1em auto; text-align: left; } .habit-icon { vertical-align: middle; padding-bottom: 3px; font-weight: 600; } .detail-label { font-weight: 500; } .detail-options { position: absolute; top: 14px; right: 10px; } .detail-options mat-icon { padding-right: 5px; } button:hover, .detail-options { cursor: pointer; }
Таким образом Angular и AM предоставили нам целый набор бесплатных компонентов, стилей и поведений, что изначально сделало приложение достаточно продуманным.
- Обратите внимание на установленную нами структурную директиву
*ngFor
, которая перебирает массив привычек. - Мы также ввели элемент
mat-card
, который предоставляет удобные способы разбивки содержимого карточки, делая ее более организованной. О дочерних компонентахmat-card
можно узнать в документации.

Настройка формы
Для управления списком привычек нам понадобится форма, которая позволит добавлять и редактировать записи.
Пока что мы поместим HTML-фрагмент для формы между кодом панели инструментов и All Habits (обратите внимание, чтобы он оказался над областью тега div с class="all-habits"
):
<div class="add-form-container"> <mat-card> <mat-card-title>Add New Habit </mat-card-title> <hr /> <form> <mat-card-content> <mat-form-field appearance="fill"> <mat-label>Title</mat-label> <input matInput /> </mat-form-field> <br /> <mat-form-field appearance="fill"> <mat-label>Frequency</mat-label> <mat-select> <mat-option value="Daily">Daily</mat-option> <mat-option value="Weekly">Weekly</mat-option> <mat-option value="Monthly">Monthly</mat-option> </mat-select> </mat-form-field> <br /> <mat-form-field appearance="fill"> <mat-label>Description</mat-label> <textarea matInput placeholder="Why is this habit important to you?" ></textarea> </mat-form-field> </mat-card-content> <mat-card-actions> <button mat-raised-button color="accent" type="submit">Save</button> <button mat-raised-button>Cancel</button> </mat-card-actions> </form> </mat-card> </div>
Для исправления красных волнистых подчеркиваний, выдаваемых элементами формы и кнопок, импортируйте MatButtonModule, MatInputModule и MatSelectModule в корневой модуль:
//...другие импорты import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatToolbarModule } from '@angular/material/toolbar'; @NgModule({ declarations: [AppComponent], imports: [ //...другие импорты MatButtonModule, MatCardModule, MatIconModule, MatInputModule, MatSelectModule, MatToolbarModule, ], providers: [], bootstrap: [AppComponent], }) export class AppModule {}
Стилизуем мы это все, добавив в app.component.scss
следующее:
.add-form-container { padding: 4em; text-align: center; max-width: 400px; margin: auto; } .add-form-container mat-card-title { margin: 1em !important; } .add-form-container button { margin-top: 10px !important; } mat-card-content { margin-bottom: 0; } form { padding-top: 1em; } mat-form-field { width: 90%; } button { width: 80%; max-width: 300px; margin-bottom: 1em; } button:hover, .detail-options { cursor: pointer; }
Форма готова! Давайте кое-что по этому фрагменту проясним:
- В
mat-form-field
есть много прекрасных вариантов для плейсхолдеров, ярлыков и стилей. Подробности в документации. - Вместо отдельного именования входных элементов AM вы добавляете их как директивы или атрибуты в обычные входные элементы HTML, как мы видим в случае с
textarea
в коде выше:<textarea matInput>
. Подробности о входных элементах Angular Material описаны в документации. - Кнопки работают аналогичным образом. Вы можете задействовать для них различные стили, которые добавляются в качестве атрибутов к элементу
<button>
. В этом случае мы используем директивуmat-raised-button
, если же вам больше по нраву отсутствие теней, то можете использоватьmat-flat-button
. Кнопки также могут иметь атрибутcolor
, в котором доступны классы цветовprimary
,ascent
иwarn
. С различными опциями настройки кнопок и стилей можете, опять же, ознакомиться в документации.

Добавление UX
Сейчас наша форма смещает список привычек вниз, и мы его не видим. А нужно ли нам его видеть в процессе добавления новой привычки? На мой взгляд, нет.
Для начала обратимся к TypeScript коду и добавим переменную, указывающую, находимся ли мы в режиме добавления привычки, определив ее по умолчанию как false:
export class AppComponent { public adding = false; public habits: Habit[] = [ <Habit>{ name: '15 Minute Walk', frequency: 'Daily', description: 'This habit makes my kitchen look nice and makes my day better the next morning.', }, <Habit>{ name: 'Weed the Garden', frequency: 'Weekly', description: 'The weeds get so out of hand if they wait any longer, and I like how nice our home looks with a clean lawn.', }, ]; }
Теперь, чтобы не отображать постоянно форму, добавим кнопку Add New Habit, которая эту форму будет вызывать. Для этого обновим раздел all-habits
:
<div class="all-habits"> <h1>All Habits</h1> <button mat-raised-button color="accent" (click)="adding = !adding"> Add New Habit </button> <div *ngFor="let habit of habits"> <mat-card> <mat-card-title> <mat-icon class="habit-icon" color="accent" aria-hidden="false" aria-label="circle check mark icon" >check_circle_outline</mat-icon > {{ habit.name }} </mat-card-title> <mat-card-content> <p> <span class="detail-label">Frequency:</span> {{ habit.frequency }} </p> <p> <span class="detail-label">Why is this habit important to me?</span> <br />{{ habit.description }} </p> </mat-card-content> </mat-card> </div> </div>
Обратите внимание, что для кнопки присутствует событие клика. При нажатии на нее, активируется переменная adding
, позволяя нам переключаться между режимом просмотра привычек и их добавления. Чтобы все это организовать нужным образом, мы используем структурную директиву *ngif
, с помощью которой будем скрывать список All Habits и показывать форму, когда adding
будет true
:
<div class="add-form-container" *ngIf="adding"> <mat-card> <mat-card-title>Add New Habit</mat-card-title> <hr /> <form> <mat-card-content> <mat-form-field appearance="fill"> <mat-label>Title</mat-label> <input matInput /> </mat-form-field> <br /> <mat-form-field appearance="fill"> <mat-label>Frequency</mat-label> <mat-select> <mat-option value="Daily">Daily</mat-option> <mat-option value="Weekly">Weekly</mat-option> <mat-option value="Monthly">Monthly</mat-option> </mat-select> </mat-form-field> <br /> <mat-form-field appearance="fill"> <mat-label>Description</mat-label> <textarea matInput placeholder="Why is this habit important to you?" ></textarea> </mat-form-field> </mat-card-content> <mat-card-actions> <button mat-raised-button color="accent" type="submit">Save</button> <button mat-raised-button>Cancel</button> </mat-card-actions> </form> </mat-card> </div> <div class="all-habits" *ngIf="!adding"> <h1>All Habits</h1> <button mat-raised-button color="accent" (click)="adding = !adding"> Add New Habit </button> <div *ngFor="let habit of habits"> <mat-card> <mat-card-title> <mat-icon class="habit-icon" color="accent" aria-hidden="false" aria-label="circle check mark icon" >check_circle_outline</mat-icon > {{ habit.name }} </mat-card-title> <div class="detail-options"> <mat-icon class="habit-icon" color="primary" >edit</mat-icon > <mat-icon class="habit-icon" color="warn" >remove_circle</mat-icon > </div> <mat-card-content> <p> <span class="detail-label">Frequency:</span> {{ habit.frequency }} </p> <p> <span class="detail-label">Why is this habit important to me?</span> <br />{{ habit.description }} </p> </mat-card-content> </mat-card> </div> </div>
После сохранения изменений форма будет появляться только при нажатии кнопки.

Отлично! Теперь, когда мы выстроили все элементы UI, пришло время добавить приложению динамичности и заставить кнопки выполнять их основную работу. Я понимаю, что все это долго, но мы уже почти закончили.
Основная функциональность
Подключение реактивной формы
А теперь веселая часть. Пора привязать к нашему TypeScript-коду реактивную форму, что даст нам возможность управлять данными в списке привычек.
Для настройки этой формы добавьте в app.component.ts
следующий код:
import { Component } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { Habit } from './models/habit'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) export class AppComponent { public adding = false; public habitForm = new FormGroup({ name: new FormControl(''), frequency: new FormControl(''), description: new FormControl(''), }); public habits: Habit[] = [ <Habit>{ name: '15 Minute Walk', frequency: 'Daily', description: 'This habit makes my kitchen look nice and makes my day better the next morning.', }, <Habit>{ name: 'Weed the Garden', frequency: 'Weekly', description: 'The weeds get so out of hand if they wait any longer, and I like how nice our home looks with a clean lawn.', }, ]; public onSubmit() { this.habits.push(this.habitForm.value as Habit); this.adding = false; } }
На что обратить внимание:
- Не забудьте импортировать
FormGroup
иFormControl
из@abgular/core
. - Тип
FormGroup
предоставляет возможность управления несколькими входными элементами формы в одном месте. - Определение
FormControls
с пустыми строками подразумевает, что значение соответствующих входных элементов при инициализации формы будет пустым. Мы научимся инициализировать их со значением, когда дойдем до редактирования. - Весь богатый арсенал возможностей о Reactive Forms описан в документации.
- Обратите внимание на метод
onSubmit()
. Мы имеем возможность обратиться к значениюFormGroup
и выполняем его приведение к моделиHabit
, получая удобную автоподстановку от IntelliSense и проверку типов.
Теперь нужно прикрепить форму TypeScript к шаблону HTML.
<div class="add-form-container" *ngIf="adding"> <mat-card> <mat-card-title>Add New Habit</mat-card-title> <hr /> <form [formGroup]="habitForm" (ngSubmit)="onSubmit()"> <mat-card-content> <mat-form-field appearance="fill"> <mat-label>Title</mat-label> <input matInput formControlName="name" /> </mat-form-field> <br /> <mat-form-field appearance="fill"> <mat-label>Frequency</mat-label> <mat-select formControlName="frequency"> <mat-option value="Daily">Daily</mat-option> <mat-option value="Weekly">Weekly</mat-option> <mat-option value="Monthly">Monthly</mat-option> </mat-select> </mat-form-field> <br /> <mat-form-field appearance="fill"> <mat-label>Description</mat-label> <textarea matInput formControlName="description" placeholder="Why is this habit important to you?" ></textarea> </mat-form-field> </mat-card-content> <mat-card-actions> <button mat-raised-button color="accent" type="submit">Save</button> <button mat-raised-button>Cancel</button> </mat-card-actions> </form> </mat-card> </div>
Чтобы HTML распознал привязку [formGroup]
, нужно добавить в импорты app.module.ts
модули FormModule
и ReactiveFormModule
:
//...другие импорты import { FormsModule , ReactiveFormsModule } from '@angular/forms'; @NgModule({ declarations: [AppComponent], imports: [ //...другие импорты FormsModule, ReactiveFormsModule, ], providers: [], bootstrap: [AppComponent], }) export class AppModule {}
Теперь у нас не только есть рабочая реактивная форма, но мы также можем добавлять в список новые привычки.
После всего проделанного важно обратить внимание на следующее:
- Обязательное добавление привязки
[formGroup]
к элементуform
. (ngSubmit)
прослушивает все события клика кнопок с типомsubmit
, так что нет необходимости добавлять отдельное событие клика для кнопки save.- Имя, которое вы указываете в атрибуте
formControlName
, должно в точности соответствовать именам элементов управления, которые вы определили в TypeScript коде.
Добавление метода редактирования
На данный момент мы можем добавлять привычки, но нам еще нужно поместить в интерфейс иконку карандаша, которая будет открывать форму редактирования и обновлять соответствующую запись в списке. Сначала добавим в app.component.ts
метод редактирования:
export class AppComponent { public adding = false; public editing = false; public editingIndex: number; public habitForm = new FormGroup({ name: new FormControl(''), frequency: new FormControl(''), description: new FormControl(''), }); public habits: Habit[] = [ <Habit>{ name: '15 Minute Walk', frequency: 'Daily', description: 'This habit makes my kitchen look nice and makes my day better the next morning.', }, <Habit>{ name: 'Weed the Garden', frequency: 'Weekly', description: 'The weeds get so out of hand if they wait any longer, and I like how nice our home looks with a clean lawn.', }, ]; public onSubmit() { const habit = this.habitForm.value as Habit; if (this.editing) { this.habits.splice(this.editingIndex, 1, habit); } else { this.habits.push(habit); } this.editing = false; this.adding = false; } public setEditForm(habit: Habit, index: number) { this.habitForm.patchValue({ name: habit.name, frequency: habit.frequency, description: habit.description, }); this.editing = true; this.editingIndex = index; } }
Несколько пояснений:
- Обратите внимание на применение
patchValue
в методеsetEditForm
. Это позволяет нам напрямую устанавливать значения всей группы форм. - Также взгляните на параметры, которые мы передаем в тот же метод, — они задействуются в наших HTML-вставках. Параметр
index
будет указывать индекс передаваемой привычки, что позволит нам находить ее в существующем массиве для замены. - Подробнее о методе
.splice()
можете узнать на GeeksforGeeks. - Заметьте, что новый флаг
editing
также устанавливается в коде отправки формы, чтобы при сохранении переключаться обратно к основному списку.
Теперь перейдем к шаблону:
<div class="add-form-container" *ngIf="adding || editing"> <mat-card> <mat-card-title>Add New Habit</mat-card-title> <hr /> <form [formGroup]="habitForm" (ngSubmit)="onSubmit()"> <mat-card-content> <mat-form-field appearance="fill"> <mat-label>Title</mat-label> <input matInput formControlName="name" /> </mat-form-field> <br /> <mat-form-field appearance="fill"> <mat-label>Frequency</mat-label> <mat-select formControlName="frequency"> <mat-option value="Daily">Daily</mat-option> <mat-option value="Weekly">Weekly</mat-option> <mat-option value="Monthly">Monthly</mat-option> </mat-select> </mat-form-field> <br /> <mat-form-field appearance="fill"> <mat-label>Description</mat-label> <textarea matInput formControlName="description" placeholder="Why is this habit important to you?" ></textarea> </mat-form-field> </mat-card-content> <mat-card-actions> <button mat-raised-button color="accent" type="submit">Save</button> <button mat-raised-button>Cancel</button> </mat-card-actions> </form> </mat-card> </div> <div class="all-habits" *ngIf="!adding && !editing"> <h1>All Habits</h1> <button mat-raised-button color="accent" (click)="adding = !adding"> Add New Habit </button> <div *ngFor="let habit of habits; let i = index;"> <mat-card> <mat-card-title> <mat-icon class="habit-icon" color="accent" aria-hidden="false" aria-label="circle check mark icon" >check_circle_outline</mat-icon > {{ habit.name }} </mat-card-title> <div class="detail-options"> <mat-icon class="habit-icon" color="primary" (click)="setEditForm(habit, i)" >edit</mat-icon > <mat-icon class="habit-icon" color="warn" >remove_circle</mat-icon > </div> <mat-card-content> <p> <span class="detail-label">Frequency:</span> {{ habit.frequency }} </p> <p> <span class="detail-label">Why is this habit important to me?</span> <br />{{ habit.description }} </p> </mat-card-content> </mat-card> </div> </div>
Какие здесь изменения:
- Для редактирования списка мы используем ту же форму, что и для внесения в него элементов, поэтому просто добавили в обе проверки
*ngIf
флагediting
. - Изменения, внесенные в
*ngFor
, позволяют нам задействовать одну из очень удобных возможностей Angular — получать индекс выбранного элемента, определив содержащую его значение переменную прямо в той же строке. После мы передаем эту переменную в функциюsetEditForm()
, сообщая таким образом форме, какую привычку собираемся редактировать. - В завершении мы добавили функцию запуска редактирования в качестве события клика для кнопки иконки карандаша.

Прекрасно! Теперь займемся удалением.
Добавление метода удаления
Добавьте в TypeScript код метод onDelete()
, который по аналогии с setEditForm
будет получать индекс из *ngFor
.
export class AppComponent { public adding = false; public editing = false; public editingIndex: number; public habitForm = new FormGroup({ name: new FormControl(''), frequency: new FormControl(''), description: new FormControl(''), }); public habits: Habit[] = [ <Habit>{ name: '15 Minute Walk', frequency: 'Daily', description: 'This habit makes my kitchen look nice and makes my day better the next morning.', }, <Habit>{ name: 'Weed the Garden', frequency: 'Weekly', description: 'The weeds get so out of hand if they wait any longer, and I like how nice our home looks with a clean lawn.', }, ]; public onSubmit() { const habit = this.habitForm.value as Habit; if (this.editing) { this.habits.splice(this.editingIndex, 1, habit); } else { this.habits.push(habit); } this.editing = false; this.adding = false; } public setEditForm(habit: Habit, index: number) { this.habitForm.patchValue({ name: habit.name, frequency: habit.frequency, description: habit.description, }); this.editing = true; this.editingIndex = index; } public onDelete(index: number) { this.habits.splice(index, 1); }
Далее в шаблоне добавьте к иконке удаления событие клика:
<div class="all-habits" *ngIf="!adding && !editing"> <h1>All Habits</h1> <button mat-raised-button color="accent" (click)="adding = !adding"> Add New Habit </button> <div *ngFor="let habit of habits; let i = index;"> <mat-card> <mat-card-title> <mat-icon class="habit-icon" color="accent" aria-hidden="false" aria-label="circle check mark icon" >check_circle_outline</mat-icon > {{ habit.name }} </mat-card-title> <div class="detail-options"> <mat-icon class="habit-icon" color="primary" (click)="setEditForm(habit, i)" >edit</mat-icon > <mat-icon class="habit-icon" color="warn" (click)="onDelete(i)" >remove_circle</mat-icon > </div> <mat-card-content> <p> <span class="detail-label">Frequency:</span> {{ habit.frequency }} </p> <p> <span class="detail-label">Why is this habit important to me?</span> <br />{{ habit.description }} </p> </mat-card-content> </mat-card> </div> </div>
Теперь у нас появилась возможность удаления.

Кнопка отмены и исправление бага
Итак, мы вышли на финишную прямую и впереди настройка последних фрагментов.
Нам осталось сделать две вещи: организовать работу кнопки отмены (cancel) и попутно исправить небольшой баг. Вы могли заметить, что если после редактирования привычки сразу перейти к созданию новой, то форма заполняется данными из только что редактированной записи. Почему это происходит?
На данный момент при сохранении новых записей мы не очищаем поля формы, поэтому данные просто в них “подвисают”. Давайте создадим решение, которое будет отвечать за очищение при сохранении или нажатии кнопки отмены, которую также потребуется добавить.
В app.component.ts
мы пропишем метод exitForm()
, который используем в функции onSubmit()
:
export class AppComponent { public adding = false; public editing = false; public editingIndex: number; public habitForm = new FormGroup({ name: new FormControl(''), frequency: new FormControl(''), description: new FormControl(''), }); public habits: Habit[] = [ <Habit>{ name: '15 Minute Walk', frequency: 'Daily', description: 'This habit makes my kitchen look nice and makes my day better the next morning.', }, <Habit>{ name: 'Weed the Garden', frequency: 'Weekly', description: 'The weeds get so out of hand if they wait any longer, and I like how nice our home looks with a clean lawn.', }, ]; public onSubmit() { const habit = this.habitForm.value as Habit; if (this.editing) { this.habits.splice(this.editingIndex, 1, habit); } else { this.habits.push(habit); } this.editing = false; this.adding = false; this.exitForm(); } public setEditForm(habit: Habit, index: number) { this.habitForm.patchValue({ name: habit.name, frequency: habit.frequency, description: habit.description, }); this.editing = true; this.editingIndex = index; } public onDelete(index: number) { this.habits.splice(index, 1); } exitForm() { this.adding = false; this.editing = false; this.habitForm.reset(); } }
Наша группа форм содержит функцию reset()
, которая будет сбрасывать входные элементы в исходное состояние и обнулять все значения в полях.
Далее мы включим в форму добавления кнопку отмены с событием клика exitForm()
:
<div class="add-form-container" *ngIf="adding || editing"> <mat-card> <mat-card-title>Add New Habit</mat-card-title> <hr /> <form [formGroup]="habitForm" (ngSubmit)="onSubmit()"> <mat-card-content> <mat-form-field appearance="fill"> <mat-label>Title</mat-label> <input matInput formControlName="name" /> </mat-form-field> <br /> <mat-form-field appearance="fill"> <mat-label>Frequency</mat-label> <mat-select formControlName="frequency"> <mat-option value="Daily">Daily</mat-option> <mat-option value="Weekly">Weekly</mat-option> <mat-option value="Monthly">Monthly</mat-option> </mat-select> </mat-form-field> <br /> <mat-form-field appearance="fill"> <mat-label>Description</mat-label> <textarea matInput formControlName="description" placeholder="Why is this habit important to you?" ></textarea> </mat-form-field> </mat-card-content> <mat-card-actions> <button mat-raised-button color="accent" type="submit">Save</button> <button mat-raised-button (click)="exitForm()">Cancel</button> </mat-card-actions> </form> </mat-card> </div>
Вот и все!
Вывод
Во-первых, я благодарю вас за то, что проделали со мной этот путь! Давайте подведем его итог.
- Мы научились использовать структурные директивы Angular для управления показом заданного содержимого на основе состояния и для динамического отображения элементов списка.
- Мы использовали реактивную форму, чтобы управлять полями формы и значениями из кода TypeScript, а не в HTML-шаблоне.
- С помощью компонентов Angular Material мы легко создали приятный UI, избежав лишней ручной работы со стилями.
В завершении статьи хочу сказать, что еще больше полезной и нужной информации вы найдете в нашем Telegram-канале.
Оригинал – Jessi Pearcy: Learn Angular Basics by Building a Simple App Using Angular Material