摩拜前端重构angular_了解组件架构:重构Angular应用
摩拜前端重构angular 在本系列的第1部分中,我们学习了如何启动并运行Todo应用程序并将其部署到GitHub页面。 这样做很好,但不幸的是,整个应用程序都挤在一个组件中。 在本文中,我们将研究更模块化的组件体系结构。 我们将研究如何将单个组件分解为较小的组件的结构化树,这些较小的组件更易于理解,重用和维护。本文是SitePoint Angular 2+教程的第2部分,有关如何使用Ang...
摩拜前端重构angular
在本系列的第1部分中,我们学习了如何启动并运行Todo应用程序并将其部署到GitHub页面。 这样做很好,但不幸的是,整个应用程序都挤在一个组件中。 在本文中,我们将研究更模块化的组件体系结构。 我们将研究如何将单个组件分解为较小的组件的结构化树,这些较小的组件更易于理解,重用和维护。
本文是SitePoint Angular 2+教程的第2部分,有关如何使用Angular CLI创建CRUD应用程序。
- 第0部分— Ultimate Angular CLI参考指南
- 第1部分-启动并运行我们的Todo应用程序的第一个版本
- 第2部分-创建单独的组件以显示待办事项列表和一个待办事项
- 第3部分-更新Todo服务以与REST API通信
- 第4部分-使用Angular路由器解析数据 。
- 第5部分-添加身份验证以保护私有内容
- 第6部分—如何将Angular项目更新到最新版本。
你并不需要遵循本教程的第一部分,对第二部分是有道理的。 您可以简单地获取一份我们的仓库的副本,从第一部分中检出代码,然后以此为起点。 下面将对此进行详细说明。
快速回顾
因此,让我们更详细地看一下第一部分中介绍的内容。 我们学习了如何:
- 使用Angular CLI初始化我们的Todo应用程序
- 创建一个
Todo
类来代表单个Todo
- 创建
TodoDataService
服务以创建,更新和删除待办事项 - 使用
AppComponent
组件显示用户界面 - 将我们的应用程序部署到GitHub页面。
第1部分的应用程序架构如下所示:
我们讨论的组件标有红色边框。
在第二篇文章中,我们将把AppComponent
所做的一些工作委托给更易于理解,重用和维护的较小的组件。
我们将创建:
-
TodoListComponent
以显示TodoListComponent
列表 -
TodoListItemComponent
以显示单个待办事项 - 一个
TodoListHeaderComponent
来创建一个新的待办事项 -
TodoListFooterComponent
来显示还剩下多少个TodoListFooterComponent
。
到本文结尾,您将了解:
- Angular组件架构的基础
- 如何使用属性绑定将数据传递到组件中
- 如何使用事件侦听器侦听组件发出的事件
- 为什么将组件拆分为较小的可重用组件是一个好习惯
- 智能组件和哑组件之间的区别以及为什么保持组件哑是一个好习惯。
因此,让我们开始吧!
启动并运行
您需要在本文中遵循的第一件事是Angular CLI的最新版本。 您可以使用以下命令进行安装:
npm install -g @angular/cli@latest
如果您需要删除以前版本的Angular CLI,请按以下步骤操作:
npm uninstall -g @angular/cli angular-cli
npm cache clean
npm install -g @angular/cli@latest
之后,您将需要第一部分中的代码副本。 可从https://github.com/sitepoint-editors/angular-todo-app获得 。 本系列中的每篇文章在存储库中都有一个相应的标记,因此您可以在应用程序的不同状态之间来回切换。
我们在第一部分结尾和在本文开始的代码被标记为part-1 。 本文结尾处的代码标记为part-2 。
您可以将标签视为特定提交ID的别名。 您可以使用git checkout
在它们之间切换。 您可以在此处阅读更多内容 。
因此,要启动并运行(已安装Angular CLI的最新版本),我们将执行以下操作:
git clone git@github.com:sitepoint-editors/angular-todo-app.git
cd angular-todo-app
npm install
git checkout part-1
ng serve
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
然后访问http:// localhost:4200 / 。 如果一切顺利,您应该会看到正在运行的Todo应用程序。
原始AppComponent
让我们打开src/app/app.component.html
,看看在第一AppComponent
中完成的AppComponent
:
<section class="todoapp">
<header class="header">
<h1>Todos</h1>
<input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="newTodo.title" (keyup.enter)="addTodo()">
</header>
<section class="main" *ngIf="todos.length > 0">
<ul class="todo-list">
<li *ngFor="let todo of todos" [class.completed]="todo.complete">
<div class="view">
<input class="toggle" type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
<label>{{todo.title}}</label>
<button class="destroy" (click)="removeTodo(todo)"></button>
</div>
</li>
</ul>
</section>
<footer class="footer" *ngIf="todos.length > 0">
<span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span>
</footer>
</section>
这是src/app/app.component.ts
对应的类:
import {Component} from '@angular/core';
import {Todo} from './todo';
import {TodoDataService} from './todo-data.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [TodoDataService]
})
export class AppComponent {
newTodo: Todo = new Todo();
constructor(private todoDataService: TodoDataService) {
}
addTodo() {
this.todoDataService.addTodo(this.newTodo);
this.newTodo = new Todo();
}
toggleTodoComplete(todo: Todo) {
this.todoDataService.toggleTodoComplete(todo);
}
removeTodo(todo: Todo) {
this.todoDataService.deleteTodoById(todo.id);
}
get todos() {
return this.todoDataService.getAllTodos();
}
}
尽管我们的AppComponent
可以正常工作,但是将所有代码都保留在一个大组件中并不能很好地扩展,因此不建议这样做。
向我们的Todo应用程序添加更多功能将使AppComponent
更大,更复杂,从而使其更难以理解和维护。
因此,建议将功能委派给较小的组件。 理想情况下,较小的组件应该是可配置的,以便在业务逻辑更改时我们不必重写其代码。
例如,在本系列的第三部分中,我们将更新TodoDataService
以与REST API进行通信,并且我们希望确保在重构TodoDataService
时不必更改任何较小的组件。
如果我们查看AppComponent
模板,则可以将其基础结构提取为:
<!-- header that lets us create new todo -->
<header></header>
<!-- list that displays todos -->
<ul class="todo-list">
<!-- list item that displays single todo -->
<li>Todo 1</li>
<!-- list item that displays single todo -->
<li>Todo 2</li>
</ul>
<!-- footer that displays statistics -->
<footer></footer>
如果将此结构转换为Angular组件名称,则会得到:
<!-- TodoListHeaderComponent that lets us create new todo -->
<app-todo-list-header></app-todo-list-header>
<!-- TodoListComponent that displays todos -->
<app-todo-list>
<!-- TodoListItemComponent that displays single todo -->
<app-todo-list-item></app-todo-list-item>
<!-- TodoListItemComponent that displays single todo -->
<app-todo-list-item></app-todo-list-item>
</app-todo-list>
<!-- TodoListFooterComponent that displays statistics -->
<app-todo-list-footer></app-todo-list-footer>
让我们看看如何利用Angular的组件驱动开发的力量来实现这一目标。
更具模块化的组件体系结构-创建TodoListHeaderComponent
让我们从创建TodoListHeader
组件开始。
从项目的根源开始,我们使用Angular CLI为我们生成组件:
$ ng generate component todo-list-header
这会为我们生成以下文件:
create src/app/todo-list-header/todo-list-header.component.css
create src/app/todo-list-header/todo-list-header.component.html
create src/app/todo-list-header/todo-list-header.component.spec.ts
create src/app/todo-list-header/todo-list-header.component.ts
它会自动将TodoListHeaderComponent
添加到AppModule
声明中:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
// Automatically imported by Angular CLI
import { TodoListHeaderComponent } from './todo-list-header/todo-list-header.component';
@NgModule({
declarations: [
AppComponent,
// Automatically added by Angular CLI
TodoListHeaderComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
需要将组件添加到模块声明中,以确保模块中的所有视图模板都可以使用该组件。 Angular CLI为我们方便地添加了TodoListHeaderComponent
,因此我们不必手动添加它。
如果TodoListHeaderComponent
不在声明中,而我们在视图模板中使用了它,Angular将抛出以下错误:
Error: Uncaught (in promise): Error: Template parse errors:
'app-todo-list-header' is not a known element:
1. If 'app-todo-list-header' is an Angular component, then verify that it is part of this module.
2. If 'app-todo-list-header' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.
要了解有关模块声明的更多信息,请确保查看Angular Module FAQ 。
现在我们已经为新的TodoListHeaderComponent
生成了所有文件,我们可以将<header>
元素从src/app/app.component.html
移至src/app/todo-list-header/todo-list-header.component.html
:
<header class="header">
<h1>Todos</h1>
<input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="newTodo.title"
(keyup.enter)="addTodo()">
</header>
还将相应的逻辑添加到src/app/todo-list-header/todo-list-header.component.ts
:
import { Component, Output, EventEmitter } from '@angular/core';
import { Todo } from '../todo';
@Component({
selector: 'app-todo-list-header',
templateUrl: './todo-list-header.component.html',
styleUrls: ['./todo-list-header.component.css']
})
export class TodoListHeaderComponent {
newTodo: Todo = new Todo();
@Output()
add: EventEmitter<Todo> = new EventEmitter();
constructor() {
}
addTodo() {
this.add.emit(this.newTodo);
this.newTodo = new Todo();
}
}
而不是将TodoDataService
注入新的TodoListHeaderComponent
以保存新的待办事项,而是发出一个add
事件并将新的待办事项作为参数传递。
我们已经了解到,Angular模板语法允许我们将处理程序附加到事件。 例如,考虑以下代码:
<input (keyup.enter)="addTodo()">
这告诉Angular在输入中按下Enter键时运行addTodo()
方法。 之所以keyup.enter
,是因为keyup.enter
事件是Angular框架定义的事件。
但是,我们还可以通过创建EventEmitter并使用@Output()装饰器装饰组件,使组件发出自己的自定义事件:
import { Component, Output, EventEmitter } from '@angular/core';
import { Todo } from '../todo';
@Component({
// ...
})
export class TodoListHeaderComponent {
// ...
@Output()
add: EventEmitter<Todo> = new EventEmitter();
addTodo() {
this.add.emit(this.newTodo);
this.newTodo = new Todo();
}
}
因此,我们现在可以使用Angular的事件绑定语法在视图模板中分配事件处理程序:
<app-todo-list-header (add)="onAddTodo($event)"></app-todo-list-header>
每次我们在TodoListHeaderComponent
调用add.emit(value)
, TodoListHeaderComponent
调用add.emit(value)
onAddTodo($event)
处理函数,并且$event
等于value
。
这将我们的TodoListHeaderComponent
与TodoDataService
分离,并允许父组件决定创建新的待办事项时需要做什么。
当我们在TodoDataService
部分中更新TodoDataService
与REST API通信时,我们不必担心TodoListHeaderComponent
因为它甚至不知道TodoDataService
存在。
智能与哑组件
您可能已经听说过智能和哑组件 。 将TodoListHeaderComponent
与TodoDataService
使TodoListHeaderComponent
成为愚蠢的组件。 愚蠢的组件不知道外部发生了什么。 它仅通过属性绑定接收输入,并且仅将输出数据作为事件发出。
使用智能和哑巴组件是一个好习惯。 它极大地改善了关注点的分离,使您的应用程序更易于理解和维护。 如果您的数据库或后端API发生了变化,则不必担心笨拙的组件。 它还使您的哑组件更加灵活,使您可以在不同情况下更轻松地重用它们。 如果您的应用程序需要两次相同的组件,一次需要将其写入后端数据库,而另一次需要将其写入内存数据库,那么哑巴组件可以使您准确地完成此任务。
现在,我们已经创建了TodoListHeaderComponent
,让我们更新AppComponent
模板以使用它:
<section class="todoapp">
<!-- header is now replaced with app-todo-list-header -->
<app-todo-list-header (add)="onAddTodo($event)"></app-todo-list-header>
<section class="main" *ngIf="todos.length > 0">
<ul class="todo-list">
<li *ngFor="let todo of todos" [class.completed]="todo.complete">
<div class="view">
<input class="toggle" type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
<label>{{todo.title}}</label>
<button class="destroy" (click)="removeTodo(todo)"></button>
</div>
</li>
</ul>
</section>
<footer class="footer" *ngIf="todos.length > 0">
<span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span>
</footer>
</section>
请注意,当用户输入新的待办事项标题时,我们如何使用onAddTodo($event)
处理程序捕获TodoListHeaderComponent
发出的add
事件:
<app-todo-list-header (add)="onAddTodo($event)"></app-todo-list-header>
我们将onAddTodo()
处理函数添加到AppComponent
类中,并删除不再需要的逻辑:
import {Component} from '@angular/core';
import {Todo} from './todo';
import {TodoDataService} from './todo-data.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [TodoDataService]
})
export class AppComponent {
// No longer needed, now handled by TodoListHeaderComponent
// newTodo: Todo = new Todo();
constructor(private todoDataService: TodoDataService) {
}
// No longer needed, now handled by TodoListHeaderComponent
// addTodo() {
// this.todoDataService.addTodo(this.newTodo);
// this.newTodo = new Todo();
// }
// Add new method to handle event emitted by TodoListHeaderComponent
onAddTodo(todo: Todo) {
this.todoDataService.addTodo(todo);
}
toggleTodoComplete(todo: Todo) {
this.todoDataService.toggleTodoComplete(todo);
}
removeTodo(todo: Todo) {
this.todoDataService.deleteTodoById(todo.id);
}
get todos() {
return this.todoDataService.getAllTodos();
}
}
现在,我们已经成功地将<header>
元素和所有基础逻辑从AppComponent
移至其自己的TodoListHeaderComponent
。
TodoListHeaderComponent
是一个愚蠢的组件, AppComponent
仍然负责使用TodoDataService
存储待办事项。
接下来,让我们解决TodoListComponent
。
创建TodoListComponent
让我们再次使用Angular CLI生成我们的TodoListComponent
:
$ ng generate component todo-list
这会为我们生成以下文件:
create src/app/todo-list/todo-list.component.css
create src/app/todo-list/todo-list.component.html
create src/app/todo-list/todo-list.component.spec.ts
create src/app/todo-list/todo-list.component.ts
它还会自动将TodoListComponent
添加到AppModule
声明中:
// ...
import { TodoListComponent } from './todo-list/todo-list.component';
@NgModule({
declarations: [
// ...
TodoListComponent
],
// ...
})
export class AppModule { }
现在,我们从src/app/app.component.html
获取与待办事项列表相关HTML:
<section class="main" *ngIf="todos.length > 0">
<ul class="todo-list">
<li *ngFor="let todo of todos" [class.completed]="todo.complete">
<div class="view">
<input class="toggle" type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
<label>{{todo.title}}</label>
<button class="destroy" (click)="removeTodo(todo)"></button>
</div>
</li>
</ul>
</section>
我们还将其移至src/app/todo-list/todo-list.component.html
:
<section class="main" *ngIf="todos.length > 0">
<ul class="todo-list">
<li *ngFor="let todo of todos" [class.completed]="todo.complete">
<app-todo-list-item
[todo]="todo"
(toggleComplete)="onToggleTodoComplete($event)"
(remove)="onRemoveTodo($event)"></app-todo-list-item>
</li>
</ul>
</section>
注意,我们引入了一个尚不存在的TodoListItemComponent
。 但是,将其添加到模板中已经使我们能够探索TodoListItemComponent
应该提供的API。 这使我们在TodoListItemComponent
中编写TodoListItemComponent
更加容易,因为我们现在知道我们期望TodoListItemComponent
具有哪些输入和输出。
我们使用[todo]
输入属性语法通过todo
属性传递todo
项,并将事件处理程序附加到我们期望TodoListItemComponent
发出的事件上,例如toggleComplete
事件和remove
事件。
让我们打开src/app/todo-list/todo-list.component.ts
并添加视图模板所需的逻辑:
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Todo } from '../todo';
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html',
styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent {
@Input()
todos: Todo[];
@Output()
remove: EventEmitter<Todo> = new EventEmitter();
@Output()
toggleComplete: EventEmitter<Todo> = new EventEmitter();
constructor() {
}
onToggleTodoComplete(todo: Todo) {
this.toggleComplete.emit(todo);
}
onRemoveTodo(todo: Todo) {
this.remove.emit(todo);
}
}
为了进一步说明智能组件和哑组件之间的区别,我们还将TodoListComponent
哑组件。
首先我们定义了一个输入属性 todos
与标记它@Input()
装饰。 这使我们能够从父组件中注入todos
。
接下来,我们使用@Output()
装饰器定义两个输出事件, remove
和toggleComplete
。 请注意,我们如何将它们的类型设置为EventEmitter<Todo>
并为每个对象分配一个新的EventEmitter
实例。
EventEmitter<Todo>
类型批注是TypeScript泛型 ,它告诉TypeScript remove
和toggleComplete
都是EventEmitter
实例,它们发出的值是Todo
实例。
最后,我们使用(toggleComplete)="onToggleTodoComplete($event)"
和(remove)="onRemoveTodo($event)"
定义我们在视图中指定的onToggleTodoComplete(todo)
和onRemoveTodo(todo)
事件处理程序。
注意我们如何在视图模板中使用$event
作为参数名称,并在方法定义中使用todo
作为参数名称。 要访问Angular模板中事件的有效负载(发射值),我们必须始终使用$event
作为参数名称。
因此,通过指定(toggleComplete)="onToggleTodoComplete($event)"
在我们的视图模板,我们告诉角度使用事件负载作为第一个参数调用时onToggleTodoComplete
方法,这将在第一个参数匹配onToggleTodoComplete
方法,即todo
。
我们知道有效负载将是一个todo
实例,因此我们将onToggleTodoComplete
方法定义为onToggleTodoComplete(todo: Todo)
,这使我们的代码更易于阅读,理解和维护。
最后,我们定义事件处理程序以在接收到有效载荷时也发出toggleComplete
并remove
事件,并将todo
指定为事件有效载荷。
本质上,我们让TodoListComponent
对其子TodoListItemComponent
实例中的事件进行TodoListItemComponent
。
这使我们能够处理TodoListComponent
之外的业务逻辑,从而使TodoListComponent
保持愚蠢 ,灵活和轻便。
我们还需要在AppComponent
重命名两个方法来反映这一点:
...
export class AppComponent {
// rename from toggleTodoComplete
onToggleTodoComplete(todo: Todo) {
this.todoDataService.toggleTodoComplete(todo);
}
// rename from removeTodo
onRemoveTodo(todo: Todo) {
this.todoDataService.deleteTodoById(todo.id);
}
}
如果我们在此阶段尝试运行应用程序,Angular将抛出错误:
Unhandled Promise rejection: Template parse errors:
Can't bind to 'todo' since it isn't a known property of 'app-todo-list-item'.
1. If 'app-todo-list-item' is an Angular component and it has 'todo' input, then verify that it is part of this module.
2. If 'app-todo-list-item' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.
那是因为我们还没有创建TodoListItemComponent
。
接下来,让我们继续。
创建TodoListItemComponent
同样,我们使用Angular CLI生成TodoListItemComponent
:
$ ng generate component todo-list-item
这将生成以下文件:
create src/app/todo-list-item/todo-list-item.component.css
create src/app/todo-list-item/todo-list-item.component.html
create src/app/todo-list-item/todo-list-item.component.spec.ts
create src/app/todo-list-item/todo-list-item.component.ts
它将TodoListItemComponent
自动添加到AppModule
声明中:
// ...
import { TodoListItemComponent } from './todo-list-item/todo-list-item.component';
@NgModule({
declarations: [
// ...
TodoListItemComponent
],
// ...
})
export class AppModule { }
现在,我们可以将原始标记从<li>
内部移动到src/app/todo-list-item.component.html
:
<div class="view">
<input class="toggle" type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
<label>{{todo.title}}</label>
<button class="destroy" (click)="removeTodo(todo)"></button>
</div>
我们不必对标记进行任何更改,但必须确保事件得到正确处理,因此让我们在src/app/todo-list-item/todo-list-item.component.ts
添加必要的代码TodoListItemComponent
src/app/todo-list-item/todo-list-item.component.ts
:
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Todo } from '../todo';
@Component({
selector: 'app-todo-list-item',
templateUrl: './todo-list-item.component.html',
styleUrls: ['./todo-list-item.component.css']
})
export class TodoListItemComponent {
@Input() todo: Todo;
@Output()
remove: EventEmitter<Todo> = new EventEmitter();
@Output()
toggleComplete: EventEmitter<Todo> = new EventEmitter();
constructor() {
}
toggleTodoComplete(todo: Todo) {
this.toggleComplete.emit(todo);
}
removeTodo(todo: Todo) {
this.remove.emit(todo);
}
}
该逻辑与TodoListComponent
的逻辑非常相似。
首先我们定义一个@Input()
以便我们可以传入一个Todo
实例:
@Input() todo: Todo;
然后,我们为模板定义click事件处理程序,并在单击复选框时发出toggleComplete
事件,并在单击'X'时发出remove
事件:
@Output()
remove: EventEmitter<Todo> = new EventEmitter();
@Output()
toggleComplete: EventEmitter<Todo> = new EventEmitter();
toggleTodoComplete(todo: Todo) {
this.toggleComplete.emit(todo);
}
removeTodo(todo: Todo) {
this.remove.emit(todo);
}
请注意,我们实际上是如何不更新或删除数据的。 我们只是发出从事件TodoListItemComponent
当用户点击一个链接,完成或删除待办事项,使得我们TodoListItemComponent
也是一个愚蠢的组成部分。
记住我们如何将事件处理程序附加到TodoListComponent
模板中的这些事件:
<section class="main" *ngIf="todos.length > 0">
<ul class="todo-list">
<li *ngFor="let todo of todos" [class.completed]="todo.complete">
<app-todo-list-item
[todo]="todo"
(toggleComplete)="onToggleTodoComplete($event)"
(remove)="onRemoveTodo($event)"></app-todo-list-item>
</li>
</ul>
</section>
然后, TodoListComponent
只需从TodoListItemComponent
重新发出事件。
通过TodoListComponent
从TodoListItemComponent
冒泡事件使我们能够保持两个组件都哑巴,并确保我们在重构TodoDataService
以便与REST API进行通信时不必更新它们(在本系列的第三部分中)。
多么酷啊!
在继续之前,让我们更新AppComponent
模板以使用新的TodoListComponent
:
<section class="todoapp">
<app-todo-list-header (add)="onAddTodo($event)"></app-todo-list-header>
<!-- section is now replaced with app-todo-list -->
<app-todo-list [todos]="todos" (toggleComplete)="onToggleTodoComplete($event)"
(remove)="onRemoveTodo($event)"></app-todo-list>
<footer class="footer" *ngIf="todos.length > 0">
<span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span>
</footer>
</section>
最后,让我们解决TodoListFooterComponent
。
创建TodoListFooterComponent
同样,从项目的根源开始,我们使用Angular CLI为我们生成TodoListFooterComponent
:
$ ng generate component todo-list-footer
这将生成以下文件:
create src/app/todo-list-footer/todo-list-footer.component.css
create src/app/todo-list-footer/todo-list-footer.component.html
create src/app/todo-list-footer/todo-list-footer.component.spec.ts
create src/app/todo-list-footer/todo-list-footer.component.ts
它将TodoListFooterComponent
自动添加到AppModule
声明中:
// ...
import { TodoListFooterComponent } from './todo-list-footer/todo-list-footer.component';
@NgModule({
declarations: [
// ...
TodoListFooterComponent
],
// ...
})
export class AppModule { }
现在,将<footer>
元素从src/app/app.component.html
到src/app/todo-list-footer/todo-list-footer.component.html
:
<footer class="footer" *ngIf="todos.length > 0">
<span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span>
</footer>
我们还将相应的逻辑添加到src/app/todo-list-footer/todo-list-footer.component.ts
:
import { Component, Input } from '@angular/core';
import { Todo } from '../todo';
@Component({
selector: 'app-todo-list-footer',
templateUrl: './todo-list-footer.component.html',
styleUrls: ['./todo-list-footer.component.css']
})
export class TodoListFooterComponent {
@Input()
todos: Todo[];
constructor() {
}
}
TodoListFooterComponent
不需要任何方法。 我们仅使用@Input()
装饰器定义todos
属性,因此我们可以使用todos
属性传入todos
。
最后,让我们更新AppComponent
模板,使其也使用新的TodoListFooterComponent
:
<section class="todoapp">
<app-todo-list-header (add)="onAddTodo($event)"></app-todo-list-header>
<app-todo-list [todos]="todos" (toggleComplete)="onToggleTodoComplete($event)"
(remove)="onRemoveTodo($event)"></app-todo-list>
<app-todo-list-footer [todos]="todos"></app-todo-list-footer>
</section>
现在,我们已经成功地重构了AppComponent
以将其功能委托给TodoListHeaderComponent
, TodoListComponent
和TodoListFooterComponent
。
在总结本文之前,我们还需要进行一些更改。
移动TodoDataService提供程序
在第一部分,我们注册了TodoDataService
作为一个供应商AppComponent
:
import {Component} from '@angular/core';
import {Todo} from './todo';
import {TodoDataService} from './todo-data.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [TodoDataService]
})
export class AppComponent {
newTodo: Todo = new Todo();
constructor(private todoDataService: TodoDataService) {
}
addTodo() {
this.todoDataService.addTodo(this.newTodo);
this.newTodo = new Todo();
}
toggleTodoComplete(todo: Todo) {
this.todoDataService.toggleTodoComplete(todo);
}
removeTodo(todo: Todo) {
this.todoDataService.deleteTodoById(todo.id);
}
get todos() {
return this.todoDataService.getAllTodos();
}
}
尽管这对于我们的Todo应用程序来说效果很好,但是Angular团队建议将应用程序范围的提供程序添加到根AppModule
而不是根AppComponent
。
在AppComponent
中注册的服务仅对AppComponent
及其组件树可用。 在AppModule
中注册的服务可用于整个应用程序中的所有组件。
如果我们的Todo应用程序会增长并引入延迟加载的模块,则延迟加载的模块将无法访问TodoDataService
,因为TodoDataService
仅可用于AppComponent
及其组件树,而不能在整个应用程序中使用。
因此,我们删除TodoDataService
作为一个供应商AppComponent
:
import {Component} from '@angular/core';
import {Todo} from './todo';
import {TodoDataService} from './todo-data.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: []
})
export class AppComponent {
newTodo: Todo = new Todo();
constructor(private todoDataService: TodoDataService) {
}
addTodo() {
this.todoDataService.addTodo(this.newTodo);
this.newTodo = new Todo();
}
toggleTodoComplete(todo: Todo) {
this.todoDataService.toggleTodoComplete(todo);
}
removeTodo(todo: Todo) {
this.todoDataService.deleteTodoById(todo.id);
}
get todos() {
return this.todoDataService.getAllTodos();
}
}
接下来,将其添加为AppModule
的提供者:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { TodoDataService } from './todo-data.service';
import { TodoListComponent } from './todo-list/todo-list.component';
import { TodoListFooterComponent } from './todo-list-footer/todo-list-footer.component';
import { TodoListHeaderComponent } from './todo-list-header/todo-list-header.component';
import { TodoListItemComponent } from './todo-list-item/todo-list-item.component';
@NgModule({
declarations: [
AppComponent,
TodoListComponent,
TodoListFooterComponent,
TodoListHeaderComponent,
TodoListItemComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [TodoDataService],
bootstrap: [AppComponent]
})
export class AppModule { }
到此结束本系列的第二部分。
摘要
在第一篇文章中 ,我们学习了如何:
- 使用Angular CLI初始化我们的Todo应用程序
- 创建一个
Todo
类来代表单个Todo
- 创建
TodoDataService
服务以创建,更新和删除待办事项 - 使用
AppComponent
组件显示用户界面 - 将我们的应用程序部署到GitHub页面。
在第二篇文章中,我们重构了AppComponent
,将其大部分工作委托给:
-
TodoListComponent
以显示TodoListComponent
列表 -
TodoListItemComponent
以显示单个待办事项 - 一个
TodoListHeaderComponent
来创建一个新的待办事项 -
TodoListFooterComponent
来显示还剩下多少个TodoListFooterComponent
。
在此过程中,我们了解到:
- Angular组件架构的基础
- 如何使用属性绑定将数据传递到组件中
- 如何使用事件侦听器侦听组件发出的事件
- 如何将组件拆分为较小的可重用组件,使我们的代码更易于重用和维护
- 当我们需要重构应用程序的业务逻辑时,如何使用智能和哑巴来简化我们的生活。
这篇文章中的所有代码都可以从https://github.com/sitepoint-editors/angular-todo-app获得 。
在下一部分中,我们将重构TodoService
以与REST API通信。
因此,请继续关注第三部分!
本文由Vildan Softic同行评审。 感谢所有SitePoint的同行评审员使SitePoint内容达到最佳状态!
对于专家主导的在线Angular培训课程,您不能超越Todd Motto的Ultimate Angular。 在这里尝试他的课程 ,并使用代码SITEPOINT_SPECIAL获得50%的折扣并帮助支持SitePoint。
翻译自: https://www.sitepoint.com/understanding-component-architecture-angular/
摩拜前端重构angular
更多推荐
所有评论(0)