Components provide a way for you to organize your application. This article walks you through creating a component to handle the individual items in the list, and adding check, edit, and delete functionality. the Angular event model is covered here.
| Prerequisites: | Familiarity with the core HTML , CSS ,和 JavaScript languages, knowledge of the terminal/command line . |
|---|---|
| Objective: | To learn more about components, including how events work to handle updates. To add check, edit, and delete functionality. |
At the command line, create a component named
item
with the following CLI command:
ng generate component item
ng generate component
command creates a component and folder with the name you specify.
Here, the folder and component name is
item
.
You can find the
item
directory within the
app
文件夹。
Just as with the
AppComponent
,
ItemComponent
is made up of the following files:
item.component.html
for HTML
item.component.ts
for logic
item.component.css
for styles
You can see a reference to the HTML and CSS files in the
@Component()
decorator metadata in
item.component.ts
.
@Component({
selector: 'app-item',
templateUrl: './item.component.html',
styleUrls: ['./item.component.css'],
})
ItemComponent
can take over the task of giving the user a way to check items off as done, edit them, or delete them.
Add markup for managing items by replacing the placeholder content in
item.component.html
with the following:
<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>
<!-- This section shows only if user clicks Edit button -->
<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>
The first input is a checkbox so users can check off items when an item is complete.
The double curly braces,
{{}}
, in the
<input>
and
<label>
for the checkbox signifies Angular's interpolation.
Angular uses
{{item.description}}
to retrieve the description of the current
item
从
项
数组。
The next section explains how components share data in detail.
The next two buttons for editing and deleting the current item are within a
<div>
.
On this
<div>
是
*ngIf
, a built-in Angular directive that you can use to dynamically change the structure of the DOM.
This
*ngIf
means that if
editable
is
false
, this
<div>
is in the DOM. If
editable
is
true
, Angular removes this
<div>
from the 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>
When a user clicks the
编辑
button,
editable
becomes true, which removes this
<div>
and its children from the DOM.
If, instead of clicking
编辑
, a user clicks
删除
,
ItemComponent
raises an event that notifies the
AppComponent
of the deletion.
An
*ngIf
is also on the next
<div>
, but is set to an
editable
value of
true
.
In this case, if
editable
is
true
, Angular puts the
<div>
and its child
<input>
and
<button>
elements in the DOM.
<!-- This section shows only if user clicks Edit button -->
<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"
, the value of the
<input>
is bound to the
description
of the current item.
This binding makes the item's
description
the value of the
<input>
.
So if the
description
is
eat
,
description
is already in the
<input>
.
This way, when the user edits the item, the value of the
<input>
is already
eat
.
The template variable,
#editedItem
, on the
<input>
means that Angular stores whatever a user types in this
<input>
in a variable called
editedItem
.
keyup
event calls the
saveItem()
method and passes in the
editedItem
value if the user chooses to press enter instead of click
保存
.
When a user clicks the
取消
button,
editable
toggles to
false
, which removes the input and buttons for editing from the DOM.
当
editable
is
false
, Angular puts
<div>
采用
编辑
and
删除
buttons back in the DOM.
点击
保存
button calls the
saveItem()
方法。
saveItem()
method takes the value from the
#editedItem
<input>
and changes the item's
description
to
editedItem.value
字符串。
In the next section, you will add code that relies on communication the
AppComponent
和
ItemComponent
.
Configure the AppComponent first by adding the following to
app.component.ts
:
remove(item) {
this.allItems.splice(this.allItems.indexOf(item), 1);
}
remove()
method uses the JavaScript
Array.splice()
method to remove one item at at the
indexOf
the relevant item.
In plain English, this means that the
splice()
method removes the item from the array.
对于更多信息有关
splice()
method, see the MDN Web Docs article on
Array.prototype.splice()
.
要使用
ItemComponent
UI, you must add logic to the component such as functions, and ways for data to go in and out.
在
item.component.ts
, edit the JavaScript imports as follows:
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Item } from "../item";
The addition of
输入
,
输出
,和
EventEmitter
allows
ItemComponent
to share data with
AppComponent
.
By importing
Item
,
ItemComponent
can understand what an
item
is.
Further down
item.component.ts
, replace the generated
ItemComponent
class with the following:
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
property helps toggle a section of the template where a user can edit an item.
editable
is the same property in the HTML as in the
*ngIf
statement,
*ngIf="editable"
.
When you use a property in the template, you must also declare it in the class.
@Input()
,
@Output()
,和
EventEmitter
facilitate communication between your two components.
An
@Input()
serves as a doorway for data to come into the component, and an
@Output()
acts as a doorway for data to go out of the component.
An
@Output()
has to be of type
EventEmitter
, so that a component can raise an event when there's data ready to share with another component.
使用
@Input()
to specify that the value of a property can come from outside of the component.
使用
@Output()
in conjunction with
EventEmitter
to specify that the value of a property can leave the component so that another component can receive that data.
saveItem()
method takes as an argument a
description
.
description
is the text that the user enters into the HTML
<input>
when editing an item in the list.
This
description
is the same string from the
<input>
采用
#editedItem
template variable.
If the user doesn't enter a value but clicks
保存
,
saveItem()
returns nothing and does not update the
description
.
If you didn't have this
if
statement, the user could click
保存
with nothing in the HTML
<input>
,和
description
would become an empty string.
If a user enters text and clicks save,
saveItem()
sets
editable
to false, which causes the
*ngIf
in the template to remove the edit feature and render the
编辑
and
删除
buttons again.
Though the application should compile at this point, you need to use the
ItemComponent
in
AppComponent
so you can see the new features in the browser.
Including one component within another in the context of a parent-child relationship gives you the flexibility of using components wherever you need them.
AppComponent
serves as a shell for the application where you can include other components.
要使用
ItemComponent
in
AppComponent
, put the
ItemComponent
selector in the
AppComponent
template.
Angular specifies the selector of a component in the metadata of the
@Component()
decorator.
In this example, the selector is
app-item
:
@Component({
selector: 'app-item',
templateUrl: './item.component.html',
styleUrls: ['./item.component.css']
})
要使用
ItemComponent
selector within the
AppComponent
, you add the element,
<app-item>
, which corresponds to the selector you defined for the component class to
app.component.html
.
Replace the current unordered list in
app.component.html
with the following updated version:
<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>
The double curly brace syntax,
{{}}
, in the
<h2>
interpolates the length of the
项
array and displays the number.
<span>
在
<h2>
uses an
*ngIf
and
else
to determine whether the
<h2>
should say "item" or "items".
If there is only a single item in the list, the
<span>
containing "item" displays.
Otherwise, if the length of the
项
array is anything other than
1
,
<ng-template>
, which we've named
elseBlock
, with the syntax
#elseBlock
, shows instead of the
<span>
.
You can use Angular's
<ng-template>
when you don't want content to render by default.
In this case, when the length of the
项
array is not
1
,
*ngIf
shows the
elseBlock
and not the
<span>
.
<li>
uses Angular's repeater directive,
*ngFor
, to iterate over all of the items in the
项
数组。
Angular's
*ngFor
like
*ngIf
, is another directive that helps you change the structure of the DOM while writing less code.
For each
item
, Angular repeats the
<li>
and everything within it, which includes
<app-item>
.
This means that for each item in the array, Angular creates another instance of
<app-item>
.
For any number of items in the array, Angular would create that many
<li>
元素。
You can use an
*ngFor
on other elements, too, such as
<div>
,
<span>
,或
<p>
, to name a few.
AppComponent
拥有
remove()
method for removing the item, which is bound to the
remove
property in the
ItemComponent
.
item
property in the square brackets,
[]
, binds the value of
item
between the
AppComponent
和
ItemComponent
.
Now you should be able to edit and delete items from the list.
When you add or delete items, the count of the items should also change.
To make the list more user-friendly, add some styles to the
ItemComponent
.
You can use a component's style sheet to add styles specific to that component. The following CSS adds basic styles, flexbox for the buttons, and custom checkboxes.
Paste the following styles into
item.component.css
.
.item {
padding: .5rem 0 .75rem 0;
text-align: left;
font-size: 1.2rem;
}
.btn-wrapper {
margin-top: 1rem;
margin-bottom: .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: .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: .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: .15em; left: .22em;
font-size: 1.3em;
line-height: 0.8;
color: #0d8dee;
transition: all .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;
}
You should now have a styled Angular to-do list application that can add, edit, and remove items. The next step is to add filtering so that you can look at items that meet specific criteria.
最后修改: , 由 MDN 贡献者