前集回顾

上一章里我们在AppComponent里通过组合InputItemCheckableItemCounter三个组件,并通过Unidirectional Data Flow(单向数据流)的方式把她们驱动起来。今天这章,我们讲讲angular2里的service

本章源码:service

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

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

需求分析

(注意动画部分),在上一章的基础上我们加入了初始化数据,在数据加载完成前会有一个loading,数据准备好之后loading消失,列表显现。

设计use case

每章都会提一下,先设计使用场景(这种方式,我们称之为”BDD”,不了解的朋友参考以BDD手写依赖注入(dependency injection))。

重构ts/app.ts

import {Component, OnInit} from '@angular/core';

import {InputItem} from './InputItem';
import {CheckableItem, Item} from './CheckableItem';
import {Counter} from './Counter';

//引入本章主题ItemService
import {ItemService} from './ItemService';

@Component({
    selector: 'my-app',
    template: `
    <h1>My First Angular 2 App</h1>
    <input-item (onItemAdded)="addItem($event)"></input-item>
    <checkable-item *ngFor="let itemInfo of items; let i = index" [item]="itemInfo" (onItemClicked)="toggle($event, i)">
    </checkable-item>
    <p *ngIf="loading">Loading</p>
    <counter *ngIf="!loading" [items]="items"></counter>
    `,
    directives: [InputItem, CheckableItem, Counter],
    //注入ItemService
    providers: [ItemService]
})
export class AppComponent implements OnInit {

    items: Item[] = [];
    //声明loading状态,初始值为true
    loading: boolean = true;

    //通过构造器自动获取ItemService实例
    constructor(private _itemService: ItemService) { }

    //在组件初始化以后调用ItemService获取初始化数据
    ngOnInit() {
        this._itemService
            .getItems()
            .then(data => {
                //重置loading状态为false
                this.loading = false;
                //设置初始值
                this.items = data;
            });
    }

    addItem(item: Item) {
        this.items = [...this.items, item];
    }

    toggle(item: Item, index: number) {
        this.items = [
            ...this.items.slice(0, index),
            { isChecked: !item.isChecked, txt: item.txt },
            ...this.items.slice(index + 1)
        ];
    }
}

实现ItemService

touch ts/ItemService.ts

向刚创建的ts/ItemService.ts中,添加如下内容:

import {Injectable} from '@angular/core';

import {Item} from './CheckableItem';

//用Injectable装饰器声明该类可被依赖注入
@Injectable()
export class ItemService {

    //设置一个初始值数组
    private items: Item[] = [
        {
            isChecked: true,
            txt: 'Learn JavaScript'
        }, {
            isChecked: false,
            txt: 'Learn TypeScript'
        }, {
            isChecked: false,
            txt: 'Learn Angular2'
        }
    ];

    //提供一个方法,返回初始数据的Promise
    getItems(): Promise<Array<Item>> {
        return new Promise((resolve, reject) => {
            //这里手动做延迟是为了模拟网络请求
            setTimeout(() => {
                resolve(this.items);
            }, 1500);
        });
    }
}

查看效果

本章内容比较简单,写到这里差不多算结束了(其实还没有哦!),先来跑跑看

npm start

OK,我确信这个代码是可以运行的,那到底什么是service?我们现在来对着代码讲一讲。

什么是service

  • service是可被替换的
  • service必须通过依赖注入使用
  • service通常用作数据存取等应用中可公用逻辑部分

如何定义service

必须通过@Injectable装饰器声明

import {Injectable} from '@angular/core';

@Injectable()
export class ItemService {
}

使用service

引入service

import {ItemService} from './ItemService';

切忌不要自作多情的new她哦!!!!!

构造器获取实例

constructor(private _itemService: ItemService) { }

自动注入实例

就像directives那样,添加到@Component的metadata中

providers: [ItemService]

就这么简单,so easy 有木有?

重构

那么我们说,到这里就结束了吗?请看下面,template里有这么一段:

  • 用了*ngForitems列表化
  • 用了*ngIf控制loading的显示状态

是不是感觉有点儿矬了,如果能有个单独的ItemList组件该多好?像这样使用:

import {Component, OnInit} from '@angular/core';

import {InputItem} from './InputItem';
import {Item} from './CheckableItem';
import {ItemList} from './ItemList';
import {Counter} from './Counter';

import {ItemService} from './ItemService';

@Component({
    selector: 'my-app',
    template: `
    <h1>My First Angular 2 App</h1>
    <input-item (onItemAdded)="addItem($event)"></input-item>
    <!--
        注意这里,已经换成item-list了哦!
    -->
    <item-list [data]="items" (onItemClicked)="toggle($event)" showLoading="loading">
    </item-list>
    <counter *ngIf="!loading" [items]="items"></counter>
    `,
    directives: [InputItem, ItemList, Counter],
    providers: [ItemService]
})
export class AppComponent implements OnInit {

    items: Item[] = [];
    loading: boolean = true;

    constructor(private _itemService: ItemService) { }

    ngOnInit() {
        this._itemService
            .getItems()
            .then(data => {
                this.loading = false;
                this.items = data;
            });
    }

    addItem(item: Item) {
        this.items = [...this.items, item];
    }

    toggle(e: { item: Item, index: number }) {
        this.items = [
            ...this.items.slice(0, e.index),
            { isChecked: !e.item.isChecked, txt: e.item.txt },
            ...this.items.slice(e.index + 1)
        ];
    }
}

实现ItemList

touch ts/ItemList.ts

向刚创建的ts/ItemList.ts中,添加如下内容:

import {Component, Input, Output, EventEmitter, ChangeDetectionStrategy} from '@angular/core';

import { CheckableItem, Item } from './CheckableItem';

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush,
    selector: 'item-list',
    template: `
    <checkable-item *ngFor="let item of data; let i=index" [item]="item" (onItemClicked)="clickItem($event, i)">
    </checkable-item>
    <p *ngIf="showLoading">Loading</p>
    `,
    directives: [CheckableItem]
})
export class ItemList {
    @Input() data: Item[];
    @Input() showLoading: boolean;

    @Output() onItemClicked = new EventEmitter();

    clickItem(e: Item, i: number) {
        this.onItemClicked.emit({
            item: e,
            index: i
        });
    }
}

一切都结束了,效果仍然没有变,还是很屌的样子!!!!

下回预告:使用Routing



angular2初入眼帘之-service
2016-04-22