Erstellen einer Item-Komponente
Komponenten bieten Ihnen eine Möglichkeit, Ihre Anwendung zu organisieren. Dieser Artikel führt Sie durch die Erstellung einer Komponente zur Verwaltung der einzelnen Elemente in der Liste und zum Hinzufügen von Funktionen wie Überprüfen, Bearbeiten und Löschen. Das Angular-Ereignismodell wird hier behandelt.
Voraussetzungen: | Vertrautheit mit den grundlegenden HTML, CSS und JavaScript Sprachen, Kenntnisse über die Terminal-/Kommandozeile. |
---|---|
Ziel: | Mehr über Komponenten zu lernen, einschließlich der Funktionsweise von Ereignissen zur Verarbeitung von Aktualisierungen. Hinzufügen von Überprüfungs-, Bearbeitungs- und Löschfunktionen. |
Erstellen der neuen Komponente
Erstellen Sie in der Kommandozeile eine Komponente namens item
mit folgendem CLI-Befehl:
ng generate component item
Der Befehl ng generate component
erstellt eine Komponente und einen Ordner mit dem von Ihnen angegebenen Namen. Hier ist der Ordner- und Komponentenname item
. Sie finden das item
-Verzeichnis innerhalb des app
-Ordners:
src/app/item ├── item.component.css ├── item.component.html ├── item.component.spec.ts └── item.component.ts
Genau wie bei AppComponent
besteht ItemComponent
aus den folgenden Dateien:
item.component.html
für HTMLitem.component.ts
für Logikitem.component.css
für Stylesitem.component.spec.ts
für das Testen der Komponente
Im @Component()
Dekorator-Metadaten in item.component.ts
finden Sie einen Verweis auf die HTML- und CSS-Dateien.
@Component({
selector: 'app-item',
standalone: true,
imports: [],
templateUrl: './item.component.html',
styleUrl: './item.component.css'
})
HTML zum ItemComponent hinzufügen
Die ItemComponent
kann die Aufgabe übernehmen, dem Benutzer eine Möglichkeit zu bieten, Elemente als erledigt abzuhaken, sie zu bearbeiten oder zu löschen.
Fügen Sie das Markup zur Verwaltung von Elementen hinzu, indem Sie den Platzhalterinhalt in item.component.html
durch Folgendes ersetzen:
<div class="item">
<input
[id]="item.description"
type="checkbox"
(change)="item.done = !item.done"
[checked]="item.done" />
<label [for]="item.description">{{item.description}}</label>
@if (!editable) {
<div class="btn-wrapper">
<button class="btn" (click)="editable = !editable">Edit</button>
<button class="btn btn-warn" (click)="remove.emit()">Delete</button>
</div>
}
<!-- This section shows only if user clicks Edit button -->
@if (editable) {
<div>
<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>
Das erste Eingabefeld ist ein Kontrollkästchen, sodass Benutzer Elemente abhaken können, wenn ein Element abgeschlossen ist. Die doppelten geschweiften Klammern, {{}}
, im <label>
für das Kontrollkästchen bedeuten die Interpolation von Angular. Angular verwendet {{item.description}}
, um die Beschreibung des aktuellen item
aus dem items
-Array abzurufen. Der nächste Abschnitt erklärt, wie Komponenten Daten im Detail teilen.
Die nächsten beiden Schaltflächen zum Bearbeiten und Löschen des aktuellen Elements befinden sich in einem <div>
. In diesem <div>
befindet sich ein @if
-Block, den Sie verwenden können, um Teile einer Vorlage basierend auf einer Bedingung zu rendern. Dieses @if
bedeutet, dass wenn editable
false
ist, dieses <div>
in der Vorlage gerendert wird. Wenn editable
true
ist, entfernt Angular dieses <div>
aus dem DOM.
@if (!editable) {
<div class="btn-wrapper">
<button class="btn" (click)="editable = !editable">Edit</button>
<button class="btn btn-warn" (click)="remove.emit()">Delete</button>
</div>
}
Wenn ein Benutzer die Schaltfläche Editieren anklickt, wird editable
auf true gesetzt, wodurch dieses <div>
und seine Kinder aus dem DOM entfernt werden. Wenn ein Benutzer nicht auf Editieren klickt, sondern auf Löschen, löst die ItemComponent
ein Ereignis aus, das die AppComponent
über das Löschen benachrichtigt.
Ein @if
befindet sich auch auf dem nächsten <div>
, ist jedoch auf den editable
-Wert true
gesetzt. In diesem Fall, wenn editable
true
ist, platziert Angular die <div>
und ihre Kind-Elemente <input>
und <button>
im DOM.
<!-- This section shows only if user clicks Edit button -->
@if (editable) {
<div>
<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>
}
Mit [value]="item.description"
ist der Wert der <input>
an die description
des aktuellen Elements gebunden. Diese Bindung macht die description
des Elements zum Wert der <input>
. Wenn die description
also "essen" ist, ist die description
bereits in der <input>
. Auf diese Weise ist, wenn der Benutzer das Element bearbeitet, der Wert der <input>
bereits "essen".
Die Template-Variable #editedItem
auf der <input>
bedeutet, dass Angular alles, was ein Benutzer in diese <input>
eingibt, in einer Variablen namens editedItem
speichert. Das keyup
-Ereignis ruft die saveItem()
-Methode auf und übergibt den editedItem
-Wert, wenn der Benutzer die Eingabetaste drückt, anstatt auf Speichern zu klicken.
Wenn ein Benutzer auf die Schaltfläche Abbrechen klickt, wechselt editable
zu false
, wodurch die Eingabe und die Schaltflächen zum Bearbeiten aus dem DOM entfernt werden. Wenn editable
false
ist, platziert Angular <div>
mit den Schaltflächen Editieren und Löschen wieder im DOM.
Durch Klicken auf die Schaltfläche Speichern wird die saveItem()
-Methode aufgerufen. Die saveItem()
-Methode übernimmt den Wert des #editedItem
-Elements und ändert die description
des Elements in den editedItem.value
-String.
Vorbereitung der AppComponent
Im nächsten Abschnitt werden Sie Code hinzufügen, der die Kommunikation zwischen AppComponent
und ItemComponent
erfordert. Fügen Sie die folgende Zeile nahe der Spitze der Datei app.component.ts
ein, um das Item
zu importieren:
import { Item } from "./item";
import { ItemComponent } from "./item/item.component";
Konfigurieren Sie dann die AppComponent, indem Sie das folgende zur gleichen Klassen-Datei hinzufügen:
remove(item: Item) {
this.allItems.splice(this.allItems.indexOf(item), 1);
}
Die Methode remove()
verwendet die JavaScript-Methode Array.splice()
, um ein Element an der indexOf
des relevanten Elements zu entfernen. Auf Deutsch bedeutet dies, dass die Methode splice()
das Element aus dem Array entfernt. Weitere Informationen zur Methode splice()
finden Sie in der Array.prototype.splice()
Dokumentation.
Logik zur ItemComponent hinzufügen
Um die ItemComponent
-UI zu verwenden, müssen Sie der Komponente Logik wie Funktionen und Möglichkeiten hinzufügen, damit Daten hinein- und herausfließen können. Bearbeiten Sie in item.component.ts
die JavaScript-Imports wie folgt:
import { Component, Input, Output, EventEmitter } from "@angular/core";
import { CommonModule } from "@angular/common";
import { Item } from "../item";
Das Hinzufügen von Input
, Output
und EventEmitter
ermöglicht es ItemComponent
, Daten mit AppComponent
zu teilen. Durch das Importieren von Item
kann die ItemComponent
verstehen, was ein item
ist. Sie können das @Component
aktualisieren, um CommonModule
in app/item/item.component.ts
zu verwenden, sodass wir die @if
-Blöcke verwenden können:
@Component({
selector: 'app-item',
standalone: true,
imports: [CommonModule],
templateUrl: './item.component.html',
styleUrl: './item.component.css',
})
Weiter unten in item.component.ts
ersetzen Sie die generierte ItemComponent
-Klasse durch das folgende:
export class ItemComponent {
editable = false;
@Input() item!: Item;
@Output() remove = new EventEmitter<Item>();
saveItem(description: string) {
if (!description) return;
this.editable = false;
this.item.description = description;
}
}
Die Eigenschaft editable
hilft dabei, einen Abschnitt der Vorlage umzustellen, in dem ein Benutzer ein Element bearbeiten kann. editable
ist dieselbe Eigenschaft im HTML wie in der @if
-Anweisung, @if(editable)
. Wenn Sie eine Eigenschaft in der Vorlage verwenden, müssen Sie sie auch in der Klasse deklarieren.
@Input()
, @Output()
und EventEmitter
erleichtern die Kommunikation zwischen Ihren beiden Komponenten. Ein @Input()
dient als Tür, durch die Daten in die Komponente kommen, und ein @Output()
agiert als Tür, durch die Daten die Komponente verlassen können, sodass eine andere Komponente diese Daten empfangen kann. Ein @Output()
muss vom Typ EventEmitter
sein, damit eine Komponente ein Ereignis auslösen kann, wenn Daten bereit sind, mit einer anderen Komponente geteilt zu werden.
Hinweis:
Das !
in der Deklaration der Klassen-Eigenschaft wird als definite assignment assertion bezeichnet. Dieser Operator teilt TypeScript mit, dass das item
-Feld immer initialisiert ist und nicht undefined
, selbst wenn TypeScript dies nicht aus der Definition des Konstruktors erkennen kann. Wenn dieser Operator nicht in Ihrem Code enthalten ist und Sie strikte TypeScript-Kompilierungseinstellungen haben, wird die App nicht kompiliert.
Verwenden Sie @Input()
, um anzugeben, dass der Wert einer Eigenschaft von außerhalb der Komponente kommen kann. Verwenden Sie @Output()
in Verbindung mit EventEmitter
, um anzugeben, dass der Wert einer Eigenschaft die Komponente verlassen kann, sodass eine andere Komponente diese Daten empfangen kann.
Die saveItem()
-Methode nimmt als Argument eine description
vom Typ string
. Die description
ist der Text, den der Benutzer in das HTML-<input>
eingibt, wenn er ein Element in der Liste bearbeitet. Diese description
ist derselbe String aus der <input>
mit der #editedItem
-Template-Variable.
Wenn der Benutzer keinen Wert eingibt, aber auf Speichern klickt, gibt saveItem()
nichts zurück und aktualisiert die description
nicht. Wenn diese if
-Anweisung nicht vorhanden wäre, könnte der Benutzer auf Speichern klicken, ohne dass im HTML-<input>
etwas steht, und die description
würde zu einem leeren String werden.
Wenn ein Benutzer Text eingibt und auf speichern klickt, setzt saveItem()
editable
auf false, wodurch das @if
in der Vorlage die Bearbeitungsfunktion entfernt und die Schaltflächen Editieren und Löschen erneut anzeigt.
Obwohl die Anwendung zu diesem Zeitpunkt kompiliert sein sollte, müssen Sie die ItemComponent
in AppComponent
verwenden, damit Sie die neuen Funktionen im Browser sehen können.
Verwenden der ItemComponent in der AppComponent
Das Einschließen einer Komponente in eine andere im Zusammenhang einer Eltern-Kind-Beziehung gibt Ihnen die Flexibilität, Komponenten überall dort zu verwenden, wo Sie sie benötigen.
Die AppComponent
dient als Hülle für die Anwendung, in der Sie andere Komponenten einfügen können.
Um die ItemComponent
in AppComponent
zu verwenden, setzen Sie den ItemComponent
-Selektor in die AppComponent
-Vorlage. Angular gibt den Selektor einer Komponente in den Metadaten des @Component()
-Dekorators an. In diesem Beispiel haben wir den Selektor als app-item
definiert:
@Component({
selector: 'app-item',
// ...
Um den ItemComponent
-Selektor in der AppComponent
zu verwenden, fügen Sie das Element <app-item>
hinzu, das dem von Ihnen für die Komponentenenklasse definierten Selektor in app.component.html
entspricht. Ersetzen Sie die derzeitige ungeordnete Liste <ul>
in app.component.html
durch die folgende aktualisierte Version:
<h2>
{{items.length}}
<span> @if (items.length === 1) { item } @else { items } </span>
</h2>
<ul>
@for (item of items; track item.description) {
<li>
<app-item (remove)="remove(item)" [item]="item"></app-item>
</li>
}
</ul>
Ändern Sie die imports
in app.component.ts
, um ItemComponent
sowie CommonModule
einzuschließen:
@Component({
standalone: true,
selector: 'app-root',
templateUrl: './app.component.html',
styleUrl: './app.component.css',
imports: [CommonModule, ItemComponent],
})
Die doppelten geschweiften Klammern, {{}}
, im <h2>
interpolieren die Länge des items
-Arrays und zeigen die Anzahl an.
Das <span>
im <h2>
verwendet ein @if
und @else
, um festzustellen, ob das <h2>
"item" oder "items" sagen soll. Wenn nur ein einziges Element in der Liste ist, zeigt das <span>
"item" an. Andernfalls, wenn die Länge des items
-Arrays etwas anderes als 1
ist, zeigt das <span>
"items" an.
Das @for
- Angulars Kontrollflussblock, der verwendet wird, um alle Element im items
-Array zu iterieren. Angulars @for
, ähnlich wie @if
, ist ein weiterer Block, der Ihnen hilft, die Struktur des DOMs zu ändern, während Sie weniger Code schreiben. Für jedes item
wiederholt Angular das <li>
und alles in ihm, einschließlich <app-item>
. Das bedeutet, dass für jedes Element im Array Angular eine weitere Instanz von <app-item>
erstellt. Bei jeder Anzahl von Elementen im Array würde Angular so viele <li>
-Elemente erstellen.
Sie können andere Elemente wie <div>
, <span>
oder <p>
innerhalb eines @for
-Blocks umschließen.
Die AppComponent
hat eine remove()
-Methode zum Entfernen des Elements, die an die remove
-Eigenschaft in der ItemComponent
gebunden ist. Die item
-Eigenschaft in den eckigen Klammern, []
, bindet den Wert von i
zwischen AppComponent
und ItemComponent
.
Nun sollten Sie in der Lage sein, Elemente aus der Liste zu bearbeiten und zu löschen. Wenn Sie Elemente hinzufügen oder löschen, sollte sich auch die Anzahl der Elemente ändern. Um die Liste benutzerfreundlicher zu gestalten, fügen Sie der ItemComponent
einige Stile hinzu.
Stile zur ItemComponent hinzufügen
Sie können das Stylesheet einer Komponente verwenden, um spezifische Stile für diese Komponente hinzuzufügen. Das folgende CSS fügt grundlegende Stile, Flexbox für die Schaltflächen und benutzerdefinierte Kontrollkästchen hinzu.
Fügen Sie die folgenden Stile in item.component.css
ein.
.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 {
/* menu buttons flexbox styles */
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;
}
/* Custom checkboxes
Adapted from https://css-tricks.com/the-checkbox-hack/#custom-designed-radio-buttons-and-checkboxes */
/* Base for label styling */
[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;
}
/* checkbox aspect */
[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;
}
/* checked mark aspect */
[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;
}
/* checked mark aspect changes */
[type="checkbox"]:not(:checked) + label:after {
opacity: 0;
transform: scale(0);
}
[type="checkbox"]:checked + label:after {
opacity: 1;
transform: scale(1);
}
/* accessibility */
[type="checkbox"]:checked:focus + label:before,
[type="checkbox"]:not(:checked):focus + label:before {
border: 2px dotted blue;
}