前集回顾
在上一章里我们讲了如何在angular2
下开发一个component
(还没做的赶紧去学吧)。我们使用了Unidirectional Data Flow模式书写component
,并引入了Immutable思想,这些以前只在React里见到的设计,现在angular2
里也有体现,并且在本章中会着重讲解多components
的协作。
本章源码:multicomponents
本章使用angular2
版本为:2.4.5
,webpack
版本为: 2.2.0
先来看看我们将要完成的效果图:
需求分析
(注意动画部分),由上一章的一个component
,变成了一个输入component
、 一个遍历显示component
、 一个总结component
。画一个组件树的示意图如下:
图片描述
分析第一部分
我们将其命名为
InputItem
它由一个
input[type="text"]
和一个button
组成当点击
button
时,需要向上冒泡事件,并组合一个新的CheckableItem
随事件发送出去清空
input[type="text"]
第3步操作,也可以通过键盘敲击"回车键"完成操作
分析第二个遍历显示部分
分析第三个总结部分
我们将其命名为
Counter
它由一个
span
组成,显示总结信息它接受一个
items
参数,用来生成总结信息总结信息为:显示当前还有多少个
isChecked === false
的item
设计use case
还是老套路,先来设计这些新的components
的使用场景(这种方式,我们称之为"BDD",不了解的朋友参考以BDD手写依赖注入。
重构ts/app.ts
import {Component} from '@angular/core';
import {Item} from './CheckableItem';
@Component({
selector: 'my-app',
template: `
<h1>My First Angular 2 App</h1>
<!--
在template里,增加input-item和counter的使用
input-item里,捕获onItemAdded事件,传递给addItem方法
-->
<input-item (onItemAdded)="addItem($event)"></input-item>
<!--
使用*ngFor遍历items变量。详情:
https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ngFor
-->
<checkable-item *ngFor="let itemInfo of items; let i = index" [item]="itemInfo" (onItemClicked)="toggle($event, i)">
</checkable-item>
<!--
counter里,传入items
-->
<counter [items]="items"></counter>
`
})
export class AppComponent {
//声明items为成员变量
items: Item[] = [];
//当捕获到onItemAdded事件时,调用该方法,添加新item到items里
//注:根据Immutable策略,生成新的items
addItem(item: Item) {
this.items = [...this.items, item];
}
//点击checkable-item时,置反其isChecked属性
//注:根据Immutable策略,生成新的items
toggle(item: Item, index: number) {
this.items = [
...this.items.slice(0, index),
{ isChecked: !item.isChecked, txt: item.txt },
...this.items.slice(index + 1)
];
}
}
实现InputItem
touch ts/InputItem.ts
向刚创建的ts/InputItem.ts
中,添加如下内容:
import {Component, Output, EventEmitter, ChangeDetectionStrategy} from '@angular/core';
@Component({
//这里仍然使用OnPush策略
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'input-item',
//template里包含一个input[type="text"]和button
//外面又一个form标签是因为需求中希望回车键也可以触发操作
template: `
<form (ngSubmit)="onSubmit()">
<input type="text" [(ngModel)]="text" name="todo">
<button type="submit">Add Item</button>
</form>
`
})
export class InputItem {
//双向绑定到input[type="text"]
text: string;
//向外部冒泡的事件
@Output() onItemAdded = new EventEmitter();
//无论点击button、还是敲击回车键,都处罚添加事件
//组装一个新的item对象,
//清空text
onSubmit() {
this.onItemAdded.emit({
isChecked: false,
txt: this.text
});
this.text = '';
}
}
实现Counter
touch ts/Counter.ts
向刚创建的ts/Counter.ts
中,添加如下内容:
import {Component, OnChanges, SimpleChange, Input, ChangeDetectionStrategy} from '@angular/core';
import {Item} from './CheckableItem';
@Component({
//这里仍然使用OnPush策略
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'counter',
//template包含一个span
template: `
<span>
We have {{ length }} item{{ postFix }}
</span>
`
})
export class Counter implements OnChanges {
//接受items参数
@Input() items: Item[];
postFix: string;
length: number;
//每次当参数items的reference发生变化时,触发该方法
//获取新的length、postFix,重绘组件
//这里和React中的componentWillUpdate很相似
ngOnChanges(changes: { [key: string]: SimpleChange }): any {
let newItems: Item[] = changes['items'].currentValue;
this.length = newItems.reduce((p, item) => p + (item.isChecked ? 0 : 1), 0);
this.postFix = this.length > 1 ? 's' : '';
}
}
修改CheckableItem
import {Component, Input, Output, EventEmitter, ChangeDetectionStrategy} from '@angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'checkable-item',
styles: [`
.deleted{
text-decoration: line-through;
}
`],
template: `
<div>
<input type="checkbox" [checked]="item.isChecked" (change)="clickItem($event)">
<label [class.deleted]="item.isChecked">{{ item.txt }}</label>
</div>
`
})
export class CheckableItem {
@Input() item: Item;
@Output() onItemClicked = new EventEmitter();
clickItem(e: MouseEvent) {
e.preventDefault();
this.onItemClicked.emit(this.item);
}
}
export interface ToggleItemHandler {
(item: Item): void;
}
export interface Item {
isChecked?: boolean;
txt?: string;
}
组件树的整体编写思路就是Unidirectional Data Flow,所以数据的变更都是Immutable的。如果之前写过React,那对于这种书写方式一定无比熟悉。每次数据的变更,无论是InputItem
还是CheckableItem
,都将变化冒泡到AppComponent
,然后由AppComponent
再向下逐级推送各组件是否重绘。
引入声明
打开index.ts
,增加新模块声明引入
import 'core-js/es6';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import {CheckableItem} from './CheckableItem';
import {InputItem} from './InputItem';
import {Counter} from './Counter';
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent, CheckableItem, InputItem, Counter ],
bootstrap: [ AppComponent ]
})
class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
OK,代码写到这里基本就结束了,看看效果吧
npm start
你又看到了伟大的效果:
下回预告:使用service
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。