前集回顾
在上一章里我们讲了如何在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步操作,也可以通过键盘敲击"回车键"完成操作
分析第二个遍历显示部分
分析第三个总结部分
设计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
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
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,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);
npm start
你又看到了伟大的效果:
下回预告:使用service