This page was translated from English by the community. Learn more and join the MDN Web Docs community.

View in English Always switch to English

Создание компонента элемента (item component)

Компоненты представляют способ для организации вашего приложения. В этой статье описывается создание компонента для обработки отдельных элементов в списке и добавление функций проверки, редактирования и удаления.

Необходимые условия: Понимание основ HTML, CSS и JavaScript знание терминала/командной строки
Цель: Изучить больше информации о компонентах, включая обработку событий, чтобы реализовать функции проверки, редактирования и удаления.

Создание нового компонента

Создайте компонент с именем item с помощью следующих команд в командной строке:

bash
ng generate component item

Команда ng generate component создаст компонент и папку с указаным именем. Имя папки и компонента будет item. Вы можете найти дирректорию item внутри папки app.

Как и в случае с AppComponent, ItemComponent состоит из следующих файлов:

  • item.component.html для HTML
  • item.component.ts для логики
  • item.component.css для стилей

Можно увидеть ссылку на файлы HTML и CSS в метаданных декоратора @Component() в item.component.ts.

js
@Component({
  selector: 'app-item',
  templateUrl: './item.component.html',
  styleUrls: ['./item.component.css'],
})

Добавьте HTML для ItemComponent

С помощью компонента ItemComponent можно будет отмечать элементы списка как выполненные, редактировать или удалять их.

Добавьте разметку для управления элементами, заменив содержимое item.component.html следующим кодом:

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.

html
<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.

html
<!-- Этот блок отображается, если пользователь кликнул на кнопку 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:

js
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-импорты так:

js
import { Component, Input, Output, EventEmitter } from "@angular/core";
import { Item } from "../item";

Input, Output, и EventEmitter позволяют ItemComponent использовать данные совместно с AppComponent. Импортируя Item, ItemComponent может понять, что такое item.

В item.component.ts, замените сгенерированный класс ItemComponent следующим:

js
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:

js
@Component({
  selector: 'app-item',
  templateUrl: './item.component.html',
  styleUrls: ['./item.component.css']
})

Чтобы использовать селектор ItemComponent в AppComponent, добавьте элемент <app-item>, что соответствует селектору, добавленному в класс компонента, в app.component.html. Замените текущий неупорядоченный список в app.component.html обновлённой версией:

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.

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-компонент приложения списка дел, позволяющий добавлять, редактировать и удалять элементы. Следующий шаг — добавление фильтрации, чтобы можно было просматривать элементы соответствующие определённым критериям.

В это модуле