# 前集回顾
上一章里我们讲了如何在angular2
下开发一个component
(还没做的赶紧去学吧)。我们使用了Unidirectional Data Flow模式书写component
,并引入了Immutable思想,这些以前只在React里见到的设计,现在angular2
里也有体现,并且在本章中会着重讲解多components
的协作。
本章源码:multicomponents
本章使用angular2
版本为:2.0.0-rc.1
先来看看我们将要完成的效果图:
# 需求分析
(注意动画部分),由上一章的一个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手写依赖注入(dependency injection))。
# 重构ts/app.ts
import {Component} from '@angular/core';
//引入输入component
import {InputItem} from './InputItem';
import {CheckableItem, Item} from './CheckableItem';
//引入总结component
import {Counter} from './Counter';
@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>
`,
directives: [InputItem, CheckableItem, 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">
<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 {% raw %}{{ length }}{% endraw %} item{% raw %}{{ postFix }}{% endraw %}
</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' : '';
}
}
组件树的整体编写思路就是Unidirectional Data Flow,所以数据的变更都是Immutable的。如果之前写过React,那对于这种书写方式一定无比熟悉。每次数据的变更,无论是InputItem
还是CheckableItem
,都将变化冒泡到AppComponent
,然后由AppComponent
再向下逐级推送各组件是否重绘。
OK,代码写到这里基本就结束了,看看效果吧
npm start
你又看到了伟大的效果:
下回预告:使用service