Создание компонента элемента (item component)
Компоненты представляют способ для организации вашего приложения. В этой статье описывается создание компонента для обработки отдельных элементов в списке и добавление функций проверки, редактирования и удаления.
| Необходимые условия: | Понимание основ HTML, CSS и JavaScript знание терминала/командной строки |
|---|---|
| Цель: | Изучить больше информации о компонентах, включая обработку событий, чтобы реализовать функции проверки, редактирования и удаления. |
Создание нового компонента
Создайте компонент с именем item с помощью следующих команд в командной строке:
ng generate component item
Команда ng generate component создаст компонент и папку с указаным именем.
Имя папки и компонента будет item.
Вы можете найти дирректорию item внутри папки app.
Как и в случае с AppComponent, ItemComponent состоит из следующих файлов:
item.component.htmlдля HTMLitem.component.tsдля логикиitem.component.cssдля стилей
Можно увидеть ссылку на файлы HTML и CSS в метаданных декоратора @Component() в item.component.ts.
@Component({
selector: 'app-item',
templateUrl: './item.component.html',
styleUrls: ['./item.component.css'],
})
Добавьте HTML для ItemComponent
С помощью компонента ItemComponent можно будет отмечать элементы списка как выполненные, редактировать или удалять их.
Добавьте разметку для управления элементами, заменив содержимое item.component.html следующим кодом:
<div class="item">
<input
[id]="item.description"
type="checkbox"
(change)="item.done = !item.done"
[checked]="item.done" />
<label [for]="item.description">{{item.description}}</label>
<div class="btn-wrapper" *ngIf="!editable">
<button class="btn" (click)="editable = !editable">Edit</button>
<button class="btn btn-warn" (click)="remove.emit()">Delete</button>
</div>
<!-- Этот блок отображается, если пользователь кликнул на кнопку Edit -->
<div *ngIf="editable">
<input
class="sm-text-input"
placeholder="edit item"
[value]="item.description"
#editedItem
(keyup.enter)="saveItem(editedItem.value)" />
<div class="btn-wrapper">
<button class="btn" (click)="editable = !editable">Cancel</button>
<button class="btn btn-save" (click)="saveItem(editedItem.value)">
Save
</button>
</div>
</div>
</div>
Чекбокс позволяет пользователям отмечать элементы как выполненные.
Двойные фигурные скобки, {{}}, в <input> и <label> означают Angular-интерполяцию.
Angular использует {{item.description}} для получения описания текущего item из массива items.
В следующем разделе подробно объясняется, как компоненты обмениваются данными.
Следующие две кнопки для редактирования и удаления текущего элемента находятся внутри <div>.
К этому <div> применена *ngIf — это встроенная Angular-директива, которую вы можете использовать для динамического изменения структуры DOM.
В данном случае *ngIf означает, что если editable равен false, то <div> будет присутствовать в DOM. Если editable равен true, Angular удалит этот <div> из DOM.
<div class="btn-wrapper" *ngIf="!editable">
<button class="btn" (click)="editable = !editable">Edit</button>
<button class="btn btn-warn" (click)="remove.emit()">Delete</button>
</div>
Когда пользователь кликает на кнопку Edit, editable становится true, что удаляет <div> и его дочерние элементы из DOM.
Если вместо клика по кнопке Edit, пользователь кликает по кнопке Delete, ItemComponent вызывает событие, которое уведомляет AppComponent об удалении.
Директива *ngIf так же применяется к следующему <div>.
В этом случае, если editable равен true, Angular вставляет <div> и его дочерние элементы <input> и <button> в DOM.
<!-- Этот блок отображается, если пользователь кликнул на кнопку Edit -->
<div *ngIf="editable">
<input
class="sm-text-input"
placeholder="edit item"
[value]="item.description"
#editedItem
(keyup.enter)="saveItem(editedItem.value)" />
<div class="btn-wrapper">
<button class="btn" (click)="editable = !editable">Cancel</button>
<button class="btn btn-save" (click)="saveItem(editedItem.value)">
Save
</button>
</div>
</div>
С помощью [value]="item.description", значение <input> будет привязано к description текущего элемента.
Эта привязка делает значение description значением <input>.
Поэтому если в description находится текст eat, то он будет автоматически отображён в <input>.
Таким образом, когда пользователь начнёт редактировать элемент, в <input> уже будет находится текст eat.
Переменная шаблона #editedItem в <input> означает, что Angular хранит все, что пользователь печатает в этом <input> в переменной editedItem.
Событие keyup вызывает метод saveItem() и передаёт туда значение из editedItem, если пользователь нажмёт Enter вместо кнопки Save.
Когда пользователь нажимает кнопку Cancel, editable переключается на false, что удаляет поле для ввода и кнопки для редактирования из DOM.
Когда в editable находится значение false, Angular вставляет <div> вместе с кнопками Edit и Delete обратно в DOM.
Нажатие на кнопку Save вызывает метод saveItem().
Метод saveItem() берёт значение из переменной #editedItem <input> и изменяет описание элемента description на строку editedItem.value.
Подготовка AppComponent
В следующем разделе вы добавите код, который свяжет AppComponent и ItemComponent.
Сначала настройте AppComponent, добавив в app.component.ts:
remove(item) {
this.allItems.splice(this.allItems.indexOf(item), 1);
}
Метод remove() использует JavaScript-метод Array.splice() для удаления одного элемента, начиная с индекса, который возвращает метод indexOf.
Чтобы узнать больше о методе splice(), посмотрите статью на MDN Array.prototype.splice().
Добавление логики в ItemComponent
Чтобы использовать ItemComponent вы должны добавить к нему логику и способы ввода/вывода данных.
В item.component.ts отредактируйте JavaScript-импорты так:
import { Component, Input, Output, EventEmitter } from "@angular/core";
import { Item } from "../item";
Input, Output, и EventEmitter позволяют ItemComponent использовать данные совместно с AppComponent.
Импортируя Item, ItemComponent может понять, что такое item.
В item.component.ts, замените сгенерированный класс ItemComponent следующим:
export class ItemComponent {
editable = false;
@Input() item: Item;
@Input() newItem: string;
@Output() remove = new EventEmitter<Item>();
saveItem(description) {
if (!description) return;
this.editable = false;
this.item.description = description;
}
}
Свойство editable помогает переключать раздел шаблона, где пользователь может редактировать элемент.
editable это свойство, которое находится в директиве *ngIf: *ngIf="editable".
Когда вы используете свойство в шаблоне, вы так же должны объявить его в классе.
@Input(), @Output(), и EventEmitter облегчают связь между вашими двумя компонентами.
@Input() служит дверью, через которую данные входят в компонент, а @Output() служит дверью для выхода данных.
@Output() должен быть типа EventEmitter, чтобы компонент мог вызвать событие, когда данные готовы для отправки в другой компонент.
Используйте @Input(), чтобы указать, что значение свойства может поступать извне.
Используйте @Output() в сочетании с EventEmitter, чтобы указать, что свойство может отправить значение в другой компонент.
Метод saveItem() принимает в качестве аргумента description.
description это текст, который пользователь вводит в HTML-тэг <input> во время редактирования элемента списка.
description это та же строка из <input>, полученная через переменную шаблона #editedItem.
Если пользователь не вводит значение, но нажимает Save, saveItem() ничего не возвращает и не обновляет description.
Если бы выражения if не было, пользователь бы нажимал Save с пустым <input>, и description становился бы пустой строкой.
Если пользователь вводит текст и нажимает сохранить, saveItem() устанавливает editable в false, что заставляет *ngIf в шаблоне удалить форму редактирования и отобразить кнопки Edit и Delete снова.
Несмотря на то что приложение уже должно компилироваться, вам нужно использовать ItemComponent в AppComponent, чтобы вы могли увидеть новые функции в браузере.
Использование ItemComponent в AppComponent
Включение одного компонента в другой в контексте родительско-дочерних отношений даёт вам гибкость в использовании компонентов.
AppComponent служит оболочкой для включения других компонентов.
Чтобы использовать ItemComponent в AppComponent, вставьте селектор ItemComponent в шаблон AppComponent.
В Angular селектор компонента указывается в метаданных декоратора @Component().
В этом примере селектор это app-item:
@Component({
selector: 'app-item',
templateUrl: './item.component.html',
styleUrls: ['./item.component.css']
})
Чтобы использовать селектор ItemComponent в AppComponent, добавьте элемент <app-item>, что соответствует селектору, добавленному в класс компонента, в app.component.html.
Замените текущий неупорядоченный список в app.component.html обновлённой версией:
<h2>
{{items.length}}
<span *ngIf="items.length === 1; else elseBlock">item</span>
<ng-template #elseBlock>items</ng-template>
</h2>
<ul>
<li *ngFor="let item of items">
<app-item (remove)="remove(item)" [item]="item"></app-item>
</li>
</ul>
Двойные фигурные скобки, {{}}, в <h2> интерполируют длину массива items и отображает её.
<span> в <h2> использует *ngIf и else чтобы определить, должен ли <h2> отображать строку "item" или "items".
Если в списке всего один элемент, <span> будет содержать строку "item".
В противном случае, если длина массива items будет отличаться от 1, то вместо <span> отобразится шаблон <ng-template>, который мы назвали elseBlock с помощью синтаксиса #elseBlock.
Angular позволяет использовать <ng-template>, если вы не хотите, чтобы контент отображался по умолчанию.
В нашем случае, когда длина массива items не 1, *ngIf отображает elseBlock а не <span>.
<li> использует Angular-директиву для повторения,*ngFor, чтобы перебрать все элементы массива items.
В Angular *ngFor это что-то вроде *ngIf - ещё одна директива, которая помогает изменять структуру DOM с помощью меньшего количества кода.
Для каждого элемента item, Angular повторяет <li> и всё, что в нём находится, включая <app-item>.
Это означает, что для каждого элемента в массиве Angular создаёт новый экземпляр <app-item>.
Для любого количества элементов в массиве Angular создаёт множество элементов <li>.
Вы можете использовать *ngFor для других элементов, таких как <div>, <span> или <p> и д.р.
В AppComponent есть метод remove() для удаления элемента, связанного со свойством remove в ItemComponent.
Свойство item в квадратных скобках [] связывает значение item между AppComponent и ItemComponent.
Теперь вы можете редактировать и удалять элементы списка.
Когда вы добавляете или удаляете элементы, количество элементов так же должно изменятся.
Чтобы сделать список более удобным, добавьте немного стилей в ItemComponent.
Добавление стилей в ItemComponent
Вы можете использовать стили, чтобы изменить внешний вид компонента. Следующий CSS добавляет базовые стили, flexbox для кнопок и стилизованные чекбоксы.
Вставьте следующе стили в item.component.css.
.item {
padding: 0.5rem 0 0.75rem 0;
text-align: left;
font-size: 1.2rem;
}
.btn-wrapper {
margin-top: 1rem;
margin-bottom: 0.5rem;
}
.btn {
/* flexbox стили для кнопок меню */
flex-basis: 49%;
}
.btn-save {
background-color: #000;
color: #fff;
border-color: #000;
}
.btn-save:hover {
background-color: #444242;
}
.btn-save:focus {
background-color: #fff;
color: #000;
}
.checkbox-wrapper {
margin: 0.5rem 0;
}
.btn-warn {
background-color: #b90000;
color: #fff;
border-color: #9a0000;
}
.btn-warn:hover {
background-color: #9a0000;
}
.btn-warn:active {
background-color: #e30000;
border-color: #000;
}
.sm-text-input {
width: 100%;
padding: 0.5rem;
border: 2px solid #555;
display: block;
box-sizing: border-box;
font-size: 1rem;
margin: 1rem 0;
}
/* Пользовательские чекбоксы
Адаптировано из https://css-tricks.com/the-checkbox-hack/#custom-designed-radio-buttons-and-checkboxes */
/* Основа для стилизации лэйбла */
[type="checkbox"]:not(:checked),
[type="checkbox"]:checked {
position: absolute;
left: -9999px;
}
[type="checkbox"]:not(:checked) + label,
[type="checkbox"]:checked + label {
position: relative;
padding-left: 1.95em;
cursor: pointer;
}
/* чекбокс */
[type="checkbox"]:not(:checked) + label:before,
[type="checkbox"]:checked + label:before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 1.25em;
height: 1.25em;
border: 2px solid #ccc;
background: #fff;
}
/* галочка для чекбокса */
[type="checkbox"]:not(:checked) + label:after,
[type="checkbox"]:checked + label:after {
content: "\2713\0020";
position: absolute;
top: 0.15em;
left: 0.22em;
font-size: 1.3em;
line-height: 0.8;
color: #0d8dee;
transition: all 0.2s;
font-family: "Lucida Sans Unicode", "Arial Unicode MS", Arial;
}
/* изменение галочки чекбокса */
[type="checkbox"]:not(:checked) + label:after {
opacity: 0;
transform: scale(0);
}
[type="checkbox"]:checked + label:after {
opacity: 1;
transform: scale(1);
}
/* доступность */
[type="checkbox"]:checked:focus + label:before,
[type="checkbox"]:not(:checked):focus + label:before {
border: 2px dotted blue;
}
Резюме
Теперь у вас должен быть стилизованный Angular-компонент приложения списка дел, позволяющий добавлять, редактировать и удалять элементы. Следующий шаг — добавление фильтрации, чтобы можно было просматривать элементы соответствующие определённым критериям.
В это модуле
-
React
-
Ember
-
Vue
- Начало работы с Vue
- Создание вашего первого компонента Vue
- Отрисовка списка Vue-компонентов
- Добавление новой формы во Vue: события, методы, и модели
- Стилизация Vue-компонентов с CSS
- Использование вычисляемых свойств во Vue
- Условная отрисовка во Vue: Редактирование существующих дел
- Управление фокусом во Vue с помощью refs
- Ресурсы по Vue
-
Svelte
- Начало работы со Svelte
- Начинаем приложение списка дел c использованием Svelte
- Динамическое поведение в Svelte: работа с переменными и пропсами
- Компоненты Svelte-приложения
- Продвинутый Svelte: реактивность, жизненный цикл, доступность
- Работа хранилищами в Svelte
- Поддержка TypeScript в Svelte
- Развёртывание и следующие шаги
-
Angular