angular2初入眼帘之-多components协作

# 前集回顾

上一章里我们讲了如何在angular2下开发一个component(还没做的赶紧去学吧)。我们使用了Unidirectional Data Flow模式书写component,并引入了Immutable思想,这些以前只在React里见到的设计,现在angular2里也有体现,并且在本章中会着重讲解多components的协作。

本章源码:multicomponents

本章使用angular2版本为:2.0.0-rc.1

先来看看我们将要完成的效果图:

angular

# 需求分析

(注意动画部分),由上一章的一个component,变成了一个输入component、 一个遍历显示component、 一个总结component。画一个组件树的示意图如下:

angular

# 分析第一部分

  1. 我们将其命名为InputItem
  2. 它由一个input[type="text"]和一个button组成
  3. 当点击button时,需要向上冒泡事件,并组合一个新的CheckableItem随事件发送出去
  4. 清空input[type="text"]
  5. 第3步操作,也可以通过键盘敲击"回车键"完成操作

# 分析第二个遍历显示部分

参考上一章 关于*ngFor

# 分析第三个总结部分

  1. 我们将其命名为Counter
  2. 它由一个span组成,显示总结信息
  3. 它接受一个items参数,用来生成总结信息
  4. 总结信息为:显示当前还有多少个isChecked === falseitem

# 设计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

你又看到了伟大的效果:

angular

下回预告:使用service