Erstellen einer Item-Komponente
Komponenten bieten eine Möglichkeit, Ihre Anwendung zu organisieren. Dieser Artikel führt Sie durch das Erstellen einer Komponente, um die einzelnen Elemente in der Liste zu verwalten, und fügt Funktionen zum Überprüfen, Bearbeiten und Löschen hinzu. Das Angular-Ereignismodell wird hier behandelt.
Voraussetzungen: | Vertrautheit mit den grundlegenden HTML, CSS und JavaScript Sprachen, Kenntnis der Terminal-/Kommandozeile. |
---|---|
Ziel: | Mehr über Komponenten zu lernen, einschließlich wie Ereignisse funktionieren, um Updates zu handhaben. Überprüfen, Bearbeiten und Löschen hinzuzufügen. |
Erstellen der neuen Komponente
Erstellen Sie in der Kommandozeile eine Komponente mit dem Namen item
mit folgendem CLI-Befehl:
ng generate component item
Der Befehl ng generate component
erstellt eine Komponente und einen Ordner mit dem 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 der AppComponent
besteht die ItemComponent
aus den folgenden Dateien:
item.component.html
für HTMLitem.component.ts
für Logikitem.component.css
für Stileitem.component.spec.ts
für das Testen der Komponente
Sie können im @Component()
Dekoratormetadaten in item.component.ts
einen Verweis auf die HTML- und CSS-Dateien sehen.
@Component({
selector: "app-item",
standalone: true,
imports: [],
templateUrl: "./item.component.html",
styleUrl: "./item.component.css",
})
export class ItemComponent {
// …
}
HTML zur ItemComponent hinzufügen
Die ItemComponent
kann die Aufgabe übernehmen, dem Benutzer eine Möglichkeit zu geben, Elemente als erledigt abzuhaken, sie zu bearbeiten oder zu löschen.
Fügen Sie 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, damit Benutzer Elemente abhaken können, wenn ein Element abgeschlossen ist.
Die doppelten geschweiften Klammern, {{}}
, im <label>
für das Kontrollkästchen bezeichnen die Interpolation von Angular.
Angular verwendet {{item.description}}
, um die Beschreibung des aktuellen item
aus dem items
-Array abzurufen.
Im nächsten Abschnitt wird im Detail erklärt, wie Komponenten Daten teilen.
Die nächsten beiden Schaltflächen zum Bearbeiten und Löschen des aktuellen Elements befinden sich innerhalb eines <div>
.
Auf 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 auf die Edit-Schaltfläche klickt, wird editable
true, wodurch dieses <div>
und seine Kinder aus dem DOM entfernt werden.
Wenn ein Benutzer anstelle von Edit auf Delete klickt, löst das ItemComponent
ein Ereignis aus, das die AppComponent
über das Löschen informiert.
Ein @if
befindet sich ebenfalls auf dem nächsten <div>
, ist aber auf einen editable
Wert von true
gesetzt.
In diesem Fall, wenn editable
true
ist, fügt Angular das <div>
und seine Kind-<input>
- und <button>
-Elemente in das DOM ein.
<!-- 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 des <input>
an die description
des aktuellen Elements gebunden.
Diese Bindung macht die description
des Elements zum Wert des <input>
.
Wenn die description
also eat
ist, ist die description
bereits im <input>
.
Auf diese Weise, wenn der Benutzer das Element bearbeitet, ist der Wert des <input>
bereits eat
.
Die Template-Variable, #editedItem
, auf dem <input>
bedeutet, dass Angular alles, was ein Benutzer in dieses <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 anstelle von Save drückt.
Wenn ein Benutzer auf die Cancel-Schaltfläche klickt, wird editable
auf false
umgeschaltet, wodurch das Eingabefeld und die Schaltflächen zum Bearbeiten aus dem DOM entfernt werden.
Wenn editable
false
ist, fügt Angular das <div>
mit den Edit- und Delete-Schaltflächen wieder in das DOM ein.
Ein Klick auf die Save-Schaltfläche ruft die saveItem()
-Methode auf.
Die saveItem()
-Methode nimmt den Wert aus dem #editedItem
-Element und ändert die description
des Elements in den editedItem.value
-String.
AppComponent vorbereiten
Im nächsten Abschnitt fügen Sie Code hinzu, der auf Kommunikation zwischen der AppComponent
und der ItemComponent
basiert.
Fügen Sie die folgende Zeile nahe dem Anfang der app.component.ts
-Datei hinzu, um Item
zu importieren:
import { Item } from "./item";
import { ItemComponent } from "./item/item.component";
Konfigurieren Sie dann die AppComponent, indem Sie folgendes zur selben Datei-Klasse hinzufügen:
export class AppComponent {
// …
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.
Einfach ausgedrückt 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 Benutzeroberfläche der ItemComponent
zu verwenden, müssen Sie der Komponente Logik wie Funktionen und Möglichkeiten zum Ein- und Ausgang von Daten hinzufügen.
Bearbeiten Sie in item.component.ts
die JavaScript-Importe 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, damit wir die @if
Blöcke verwenden können:
@Component({
selector: "app-item",
standalone: true,
imports: [CommonModule],
templateUrl: "./item.component.html",
styleUrl: "./item.component.css",
})
export class ItemComponent {
// …
}
Weiter unten in item.component.ts
ersetzen Sie die generierte ItemComponent
Klasse durch 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 editable
-Eigenschaft hilft, einen Bereich der Vorlage umzuschalten, 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 diese auch in der Klasse deklarieren.
@Input()
, @Output()
und EventEmitter
erleichtern die Kommunikation zwischen Ihren beiden Komponenten.
Ein @Input()
dient als Durchgang für Daten, die in die Komponente kommen, und ein @Output()
agiert als Durchgang für Daten, die die Komponente verlassen, damit 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 Eigenschaftsdeklaration der Klasse 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 von der Definition des Konstruktors nicht ableiten kann. Wenn dieser Operator nicht in Ihrem Code enthalten ist und Sie strenge 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 Methode saveItem()
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 dem <input>
mit der #editedItem
Template-Variablen.
Gibt der Benutzer keinen Wert ein, sondern klickt auf Save, gibt saveItem()
nichts zurück und aktualisiert die description
nicht.
Wenn Sie diese if
-Anweisung nicht hätten, könnte der Benutzer auf Save klicken, ohne etwas in das HTML-<input>
einzugeben, und die description
würde ein leerer 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 erneut die Edit- und Delete-Schaltflächen rendert.
Obwohl die Anwendung zu diesem Zeitpunkt kompiliert werden sollte, müssen Sie die ItemComponent
in der AppComponent
verwenden, damit Sie die neuen Funktionen im Browser sehen können.
Verwenden der ItemComponent in der AppComponent
Die Einbindung einer Komponente in eine andere im Kontext 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 einbinden können.
Um die ItemComponent
in der AppComponent
zu verwenden, platzieren Sie den ItemComponent
-Selektor in der 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",
// ...
})
export class ItemComponent {
// …
}
Um den ItemComponent
-Selektor innerhalb der AppComponent
zu verwenden, fügen Sie das Element <app-item>
, das dem von Ihnen definierten Selektor für die Komponentenklasse entspricht, app.component.html
hinzu.
Ersetzen Sie die aktuelle 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 sowohl ItemComponent
als auch CommonModule
einzuschließen:
@Component({
standalone: true,
selector: "app-root",
templateUrl: "./app.component.html",
styleUrl: "./app.component.css",
imports: [CommonModule, ItemComponent],
})
export class AppComponent {
// …
}
Die doppelte geschweifte Klammer-Syntax, {{}}
, im <h2>
interpoliert die Länge des items
-Arrays und zeigt die Anzahl an.
Das <span>
im <h2>
verwendet ein @if
und @else
, um zu bestimmen, ob das <h2>
"item" oder "items" sagen sollte.
Wenn sich nur ein einzelnes Element in der Liste befindet, 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
- Angular's Kontrollflussblock, wird verwendet, um über alle Elemente im items
-Array zu iterieren.
Angulars @for
ähnelt @if
, ein weiterer Block, der Ihnen hilft, die Struktur des DOM zu ändern und weniger Code zu schreiben.
Für jedes item
wiederholt Angular das <li>
und alles darin, einschließlich <app-item>
.
Dies bedeutet, dass Angular für jedes Element im Array eine weitere Instanz von <app-item>
erstellt.
Für jede Anzahl von Elementen im Array würde Angular diese Anzahl von <li>
-Elementen 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, das an die remove
-Eigenschaft in der ItemComponent
gebunden ist.
Die item
-Eigenschaft in den eckigen Klammern, []
, bindet den Wert von i
zwischen der AppComponent
und der ItemComponent
.
Jetzt sollten Sie in der Lage sein, Elemente in 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 zu ItemComponent hinzufügen
Sie können das Stylesheet einer Komponente verwenden, um spezifische Stile für diese Komponente hinzuzufügen. Der folgende CSS-Code 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;
}