[Angular2] Case Study:Tour of Heroes - 多组件、服务

接着[Angular2] Case Study:Tour of Heroes - 对象、列表继续。

Hero Detail组件

接下来,将hero detail重构为一个独立的组件。

app文件夹下创建文件hero-detail.component.ts,创建HeroDetailComponent组件:

import {Component} from 'angular2/core';
@Component({
  selector: 'my-hero-detail',
})
export class HeroDetailComponent {
}

将模板从AppComponent组件中移到HeroDetailComponent中:

template: `
 <div *ngIf="hero">
 <h2>{{hero.name}} details!</h2>
 <div><label>id: </label>{{hero.id}}</div>
 <div>
 <label>name: </label>
 <input [(ngModel)]="hero.name" placeholder="name"/>
 </div>
 </div>
`,

为组件HeroDetailComponent添加属性hero

hero: Hero;

但是这时会发现Hero接口是定义在AppComponent中的,现在AppComponentHeroDetailComponent都依赖Hero接口,因此将它移到单独的模块中,在app文件夹下创建hero.ts

export interface Hero {
  id: number;
  name: string;
}

分别在AppComponentHeroDetailComponent中添加引用:

import {Hero} from './hero';

注意到。HeroDetailComponenthero属性是个输入参数,AppComponent在调用它时将selectedHero传递给它:

<my-hero-detail [hero]="selectedHero"></my-hero-detail>

hero定义为输入参数有多种方法,例如在@Component中配置inputs: ['hero'],或者使用@Input()

接下来,更新AppComponent,引用HeroDetailComponent

import {HeroDetailComponent} from './hero-detail.component';

修改模板:

template:`
 <h1>{{title}}</h1>
 <h2>My Heroes</h2>
 <ul class="heroes">
 <li *ngFor="#hero of heroes"
 [class.selected]="hero === selectedHero"
 (click)="onSelect(hero)">
 <span class="badge">{{hero.id}}</span> {{hero.name}}
 </li>
 </ul>
 <my-hero-detail [hero]="selectedHero"></my-hero-detail>
`,

这时看起来一切都已经完成了,但是点击后hero的详细信息并没有显示。因为Angular还不认识新的标签<my-hero-detail>,所以忽略它了。需要在@Component中添加指令:

directives: [HeroDetailComponent]

现在能正常运行了!

完整的app.component.ts文件如下:

import {Component} from 'angular2/core';
import {Hero} from './hero';
import {HeroDetailComponent} from './hero-detail.component';
@Component({
  selector: 'my-app',
  template:`
 <h1>{{title}}</h1>
 <h2>My Heroes</h2>
 <ul class="heroes">
 <li *ngFor="#hero of heroes"
 [class.selected]="hero === selectedHero"
 (click)="onSelect(hero)">
 <span class="badge">{{hero.id}}</span> {{hero.name}}
 </li>
 </ul>
 <my-hero-detail [hero]="selectedHero"></my-hero-detail>
 `,
  styles:[`
 .selected {
 background-color: #CFD8DC !important;
 color: white;
 }
 .heroes {
 margin: 0 0 2em 0;
 list-style-type: none;
 padding: 0;
 width: 10em;
 }
 .heroes li {
 cursor: pointer;
 position: relative;
 left: 0;
 background-color: #EEE;
 margin: .5em;
 padding: .3em 0;
 height: 1.6em;
 border-radius: 4px;
 }
 .heroes li.selected:hover {
 background-color: #BBD8DC !important;
 color: white;
 }
 .heroes li:hover {
 color: #607D8B;
 background-color: #DDD;
 left: .1em;
 }
 .heroes .text {
 position: relative;
 top: -3px;
 }
 .heroes .badge {
 display: inline-block;
 font-size: small;
 color: white;
 padding: 0.8em 0.7em 0 0.7em;
 background-color: #607D8B;
 line-height: 1em;
 position: relative;
 left: -1px;
 top: -4px;
 height: 1.8em;
 margin-right: .8em;
 border-radius: 4px 0 0 4px;
 }
 `],
  directives: [HeroDetailComponent]
})
export class AppComponent {
  title = 'Tour of Heroes';
  heroes = HEROES;
  selectedHero: Hero;
  onSelect(hero: Hero) { this.selectedHero = hero; }
}
var HEROES: Hero[] = [
  { "id": 11, "name": "Mr. Nice" },
  { "id": 12, "name": "Narco" },
  { "id": 13, "name": "Bombasto" },
  { "id": 14, "name": "Celeritas" },
  { "id": 15, "name": "Magneta" },
  { "id": 16, "name": "RubberMan" },
  { "id": 17, "name": "Dynama" },
  { "id": 18, "name": "Dr IQ" },
  { "id": 19, "name": "Magma" },
  { "id": 20, "name": "Tornado" }
];

完整的hero-detail.component.html文件如下:

import {Component} from 'angular2/core';
import {Hero} from './hero';
import {HeroDetailComponent} from './hero-detail.component';
@Component({
  selector: 'my-app',
  template:`
 <h1>{{title}}</h1>
 <h2>My Heroes</h2>
 <ul class="heroes">
 <li *ngFor="#hero of heroes"
 [class.selected]="hero === selectedHero"
 (click)="onSelect(hero)">
 <span class="badge">{{hero.id}}</span> {{hero.name}}
 </li>
 </ul>
 <my-hero-detail [hero]="selectedHero"></my-hero-detail>
 `,
  styles:[`
 .selected {
 background-color: #CFD8DC !important;
 color: white;
 }
 .heroes {
 margin: 0 0 2em 0;
 list-style-type: none;
 padding: 0;
 width: 10em;
 }
 .heroes li {
 cursor: pointer;
 position: relative;
 left: 0;
 background-color: #EEE;
 margin: .5em;
 padding: .3em 0;
 height: 1.6em;
 border-radius: 4px;
 }
 .heroes li.selected:hover {
 background-color: #BBD8DC !important;
 color: white;
 }
 .heroes li:hover {
 color: #607D8B;
 background-color: #DDD;
 left: .1em;
 }
 .heroes .text {
 position: relative;
 top: -3px;
 }
 .heroes .badge {
 display: inline-block;
 font-size: small;
 color: white;
 padding: 0.8em 0.7em 0 0.7em;
 background-color: #607D8B;
 line-height: 1em;
 position: relative;
 left: -1px;
 top: -4px;
 height: 1.8em;
 margin-right: .8em;
 border-radius: 4px 0 0 4px;
 }
 `],
  directives: [HeroDetailComponent]
})
export class AppComponent {
  title = 'Tour of Heroes';
  heroes = HEROES;
  selectedHero: Hero;
  onSelect(hero: Hero) { this.selectedHero = hero; }
}
var HEROES: Hero[] = [
  { "id": 11, "name": "Mr. Nice" },
  { "id": 12, "name": "Narco" },
  { "id": 13, "name": "Bombasto" },
  { "id": 14, "name": "Celeritas" },
  { "id": 15, "name": "Magneta" },
  { "id": 16, "name": "RubberMan" },
  { "id": 17, "name": "Dynama" },
  { "id": 18, "name": "Dr IQ" },
  { "id": 19, "name": "Magma" },
  { "id": 20, "name": "Tornado" }
];

hero.ts文件如下:

export interface Hero {
  id: number;
  name: string;
}

服务

接下来将数据的处理重构到单独的服务模块中。

app文件夹中创建文件hero.service.ts

import {Injectable} from 'angular2/core';

@Injectable()
export class HeroService {
}

服务的定义中使用了@Injectable(),它可以让Angular根据元数据实现依赖注入。虽然目前的这个服务不依赖其他模块,但始终使用@Injectable()是个很好的习惯。

添加getHeroes方法存根:

@Injectable()
export class HeroService {
  getHeroes() {
  }
}

之前在AppComponent中模拟了heroes数组,但是这个数据不应该出现在那里,将它放到一个单独的文件中,在app文件夹下创建文件mock-heroes.ts

import {Hero} from './hero';
export var HEROES: Hero[] = [
    {"id": 11, "name": "Mr. Nice"},
    {"id": 12, "name": "Narco"},
    {"id": 13, "name": "Bombasto"},
    {"id": 14, "name": "Celeritas"},
    {"id": 15, "name": "Magneta"},
    {"id": 16, "name": "RubberMan"},
    {"id": 17, "name": "Dynama"},
    {"id": 18, "name": "Dr IQ"},
    {"id": 19, "name": "Magma"},
    {"id": 20, "name": "Tornado"}
];

app.component.ts中的HEROES数组移除,并且将heroes属性改为:

heroes: Hero[];

修改HeroService,从mock-heroes中获取数据:

import {HEROES} from './mock-heroes';
import {Injectable} from 'angular2/core';

@Injectable()
export class HeroService {
  getHeroes() {
    return HEROES;
  }
}

AppComponent中使用HeroService,首先添加引用:

import {HeroService} from './hero.service';

那么,现在如何初始化HeroService呢?建议构造函数依赖注入:

constructor(private _heroService: HeroService) { }

这个构造函数什么都没做,只是定义了一个HeroService类型的属性_heroService

同时还要注册provider,在AppComponent@Copmonent中添加配置:

providers: [HeroService]

如果子组件HeroDetailComponent需要使用AppComponentHeroService,直接在构造函数里注入就行了,不再需要注册provider。也就是说子组件不要重复父组件的providers。

AppComponent中添加getHeroes方法:

getHeroes() {
   this.heroes = this._heroService.getHeroes();
 }

那么AppComponent在哪里调用getHeroes方法呢?构造函数中?更好的建议是使用lifecycle hook,以保证构造函数中的逻辑尽量少。

OnInit接口的使用方式为:

```javascript
import {OnInit} from 'angular2/core';

export class AppComponent implements OnInit {
ngOnInit() {
this.getHeroes();
}
}
```

异步服务和promise

目前HeroServicegetHeroes是同步的,直接从模拟的数据中读取并立即返回结果。接下来使用promise将它改成异步方式。

修改HeroService中的getHeroes方法:

getHeroes() {
  return Promise.resolve(HEROES);
}

修改AppComponent中的getHeroes方法:

getHeroes() {
  this._heroService.getHeroes().then(heroes => this.heroes = heroes);
}

至此,服务的改造就完成了。

完整的hero.service.ts文件如下:

import {Hero} from './hero';
import {HEROES} from './mock-heroes';
import {Injectable} from 'angular2/core';
@Injectable()
export class HeroService {
  getHeroes() {
    return Promise.resolve(HEROES);
  }
  // See the "Take it slow" appendix
  getHeroesSlowly() {
    return new Promise<Hero[]>(resolve =>
      setTimeout(()=>resolve(HEROES), 2000) // 2 seconds
    );
  }
}

完整的app.component.ts如下:

import {Component, OnInit} from 'angular2/core';
import {Hero} from './hero';
import {HeroDetailComponent} from './hero-detail.component';
import {HeroService} from './hero.service';
@Component({
  selector: 'my-app',
  template:`
 <h1>{{title}}</h1>
 <h2>My Heroes</h2>
 <ul class="heroes">
 <li *ngFor="#hero of heroes"
 [class.selected]="hero === selectedHero"
 (click)="onSelect(hero)">
 <span class="badge">{{hero.id}}</span> {{hero.name}}
 </li>
 </ul>
 <my-hero-detail [hero]="selectedHero"></my-hero-detail>
 `,
  styles:[`
 .selected {
 background-color: #CFD8DC !important;
 color: white;
 }
 .heroes {
 margin: 0 0 2em 0;
 list-style-type: none;
 padding: 0;
 width: 10em;
 }
 .heroes li {
 cursor: pointer;
 position: relative;
 left: 0;
 background-color: #EEE;
 margin: .5em;
 padding: .3em 0;
 height: 1.6em;
 border-radius: 4px;
 }
 .heroes li.selected:hover {
 background-color: #BBD8DC !important;
 color: white;
 }
 .heroes li:hover {
 color: #607D8B;
 background-color: #DDD;
 left: .1em;
 }
 .heroes .text {
 position: relative;
 top: -3px;
 }
 .heroes .badge {
 display: inline-block;
 font-size: small;
 color: white;
 padding: 0.8em 0.7em 0 0.7em;
 background-color: #607D8B;
 line-height: 1em;
 position: relative;
 left: -1px;
 top: -4px;
 height: 1.8em;
 margin-right: .8em;
 border-radius: 4px 0 0 4px;
 }
 `],
  directives: [HeroDetailComponent],
  providers: [HeroService]
})
export class AppComponent implements OnInit {
  title = 'Tour of Heroes';
  heroes: Hero[];
  selectedHero: Hero;
  constructor(private _heroService: HeroService) { }
  getHeroes() {
    this._heroService.getHeroes().then(heroes => this.heroes = heroes);
  }
  ngOnInit() {
    this.getHeroes();
  }
  onSelect(hero: Hero) { this.selectedHero = hero; }
}

mock-heroes.ts如下:

import {Hero} from './hero';
export var HEROES: Hero[] = [
    {"id": 11, "name": "Mr. Nice"},
    {"id": 12, "name": "Narco"},
    {"id": 13, "name": "Bombasto"},
    {"id": 14, "name": "Celeritas"},
    {"id": 15, "name": "Magneta"},
    {"id": 16, "name": "RubberMan"},
    {"id": 17, "name": "Dynama"},
    {"id": 18, "name": "Dr IQ"},
    {"id": 19, "name": "Magma"},
    {"id": 20, "name": "Tornado"}
];

PS:在HeroServise中添加getHeroesSlowly方法模拟缓慢的处理:

getHeroesSlowly() {
  return new Promise<Hero[]>(resolve =>
    setTimeout(()=>resolve(HEROES), 2000) // 2 seconds
  );
}

参考资料

Angular2官方文档

你可能感兴趣的:([Angular2] Case Study:Tour of Heroes - 多组件、服务)