第七章 模块

点此处可以下载本章完成后的 示例代码 。运行方法为:

  1. 解压缩文件,进入heroes项目根目录。
  2. 使用npm install补上依赖包。也可以把之前备份的node_modules目录复制一份在项目根目录下。
  3. 使用json-server data.json启动后端服务。
  4. 使用npm start运行项目,打开浏览器访问http://localhost:4200/进入项目。

到现在为止,我们的主角都一一登场了,组件、路由、服务……已经可以搭建一个简单的典型CRUD应用(指主要对数据库执行Create, Read, Update,Delete操作的应用)了。本章将完善英雄应用,增加新增英雄、删除英雄和查询英雄等功能。由于我们的代码越来越多,在正式操作以前,我们引入一个新的概念:模块,来组织我们的代码文件,使代码更容易维护。

1. 创建模块

Angular应用创建之初,就会自动创建一个模块:根模块,即app.module.ts。对于小型应用而言,这也够用了。但是,应用稍微大一点以后,组件就需要进行有效的整合管理,将不同的功能组件放在不同的模块中,就显得很必要了。

首先,我们要在src/app/heroes目录下,创建一个模块文件,名为heroes.module.ts。这个文件的作用是管理hero相关的组件和服务,所以同时应该把app.module.ts中hero相关的东西删掉,换上这个模块文件。

因为下面第2步要创建heroes自己的路由模块,所以在heroes.module.ts中先加上对heroes-routing..module.ts的引用。

heroes.module.ts

import { NgModule }       from '@angular/core';
import { CommonModule }   from '@angular/common';
import { FormsModule }    from '@angular/forms';

import { HeroListComponent }    from './hero-list/hero-list.component';
import { HeroDetailComponent }  from './hero-detail/hero-detail.component';
import { HeroesRoutingModule }  from './heroes-routing.module';
import { HeroService } from '../service/hero.service';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    HeroesRoutingModule
  ],
  declarations: [
    HeroListComponent,
    HeroDetailComponent
  ],
  providers: [ HeroService ]
})
export class HeroesModule {}

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { ApiService } from './service/api.service';
import { AppRoutingModule } from './app-routing.module';

import { HeroesModule } from './heroes/heroes.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    HeroesModule,
    AppRoutingModule
  ],
  providers: [
      ApiService
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

2. 创建独立的路由模块

随着英雄指南的功能不断增加,路由也会变得越来越复杂,这时我们就需要把相关的路由信息从app-routing.module.ts中拿出来,放到一个独立的路由模块中,这样再维护越来就会比较方便。我们仍然选择在src/app/heroes目录下,新建一个heroes-routing.module.ts文件。相应的,删掉app-routing.module.ts中与hero有关的路由。

heroes-routing.module.ts

import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

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

const routes: Routes = [
  { path: 'heroes', component: HeroListComponent },
  { path: 'hero/:id', component: HeroDetailComponent } //增加英雄详情的路由
];

@NgModule({
  imports: [ RouterModule.forChild(routes) ],  //注意此处是forChild
  exports: [ RouterModule ]
})
export class HeroesRoutingModule {}

app-routing.module.ts

import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  { path: '', redirectTo: '/heroes', pathMatch: 'full' }
];

@NgModule({
  imports: [ RouterModule.forRoot(routes) ],
  exports: [ RouterModule ]
})
export class AppRoutingModule {}

3. 修改 hero-list

我们之前是把英雄详情放在英雄列表的下方,这样如果列表很长,就不容易找到了,我们打算用一个单独的页面去展示英雄的详情。

src/app/heroes/hero-list/hero-list.component.html的最下方,删除<app-hero-detail [hero]="selectedHero"></app-hero-detail>这一行。我们以后不在这里显示英雄详情了。

hero-list.component.html

<h2>My Heroes</h2>
<div>
  <label>Hero name:</label> <input #heroName />
  <button (click)="add(heroName.value); heroName.value=''">
    Add
  </button>
</div>
<ul class="heroes">
  <li *ngFor="let hero of heroes" (click)="onSelect(hero)"
      [class.selected]="hero === selectedHero">
    <span class="badge">{{hero.id}}</span>
    <span>{{hero.name}}</span>
    <button class="delete"
      (click)="delete(hero); $event.stopPropagation()">x</button>
  </li>
</ul>

修改src/app/heroes/hero-list/hero-list.component.ts,增加add, delete方法,修改onSelect方法。

hero-list.component.ts

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

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

@Component({
  selector: 'app-hero-list',
  templateUrl: './hero-list.component.html',
  styleUrls: [ './hero-list.component.css' ]
})
export class HeroListComponent implements OnInit {
  heroes: Hero[];
  selectedHero: Hero;

  constructor(
    private heroService: HeroService,
    private router: Router) { }

  getHeroes(): void {
    this.heroService
        .getHeroes()
        .then(heroes => this.heroes = heroes);
        }

  add(name: string): void {
    name = name.trim();
    if (!name) { return; }
    this.heroService.create(name)
      .then(hero => {
        this.heroes.push(hero);
        this.selectedHero = null;
      });
  }

  delete(hero: Hero): void {
    this.heroService
        .delete(hero.id)
        .then(() => {
          this.heroes = this.heroes.filter(h => h !== hero);
          if (this.selectedHero === hero) { this.selectedHero = null; }
        });
  }
  ngOnInit(): void {
    this.getHeroes();
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
    this.gotoDetail();
  }

  gotoDetail(): void {
    this.router.navigate(['/hero', this.selectedHero.id]);
  }
}

相应的样式文件中,增加对button的样式。

hero-list.component.css

button {
  font-family: Arial;
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
  cursor: hand;
}
button:hover {
  background-color: #cfd8dc;
}
button.delete {
  float:right;
  margin-top: 2px;
  margin-right: .8em;
  background-color: gray !important;
  color:white;
}

4. 修改 hero-detail

在之前的HeroDetailComponent中,我们是从父组件HeroComponent@Input()的方式获得了要显示的对象。现在我们要修改它获得数据的方式。现在,我们将其改为从路径中获得要显示的英雄的ID。

src/app/heroes/hero-detail/hero-detail.component.ts


import { Component, OnInit }        from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Location }                 from '@angular/common';
import 'rxjs/add/operator/switchMap';
import { Hero }        from '../../domain/hero';
import { HeroService } from '../../service/hero.service';

@Component({
  selector: 'app-hero-detail',
  templateUrl: './hero-detail.component.html',
  styleUrls: [ './hero-detail.component.css' ]
})
export class HeroDetailComponent implements OnInit {
  hero: Hero;

  constructor(
    private heroService: HeroService,
    private route: ActivatedRoute,
    private location: Location
  ) {}
  ngOnInit(): void {
    this.route.paramMap
      .switchMap((params: ParamMap) => this.heroService.getHero(+params.get('id')))
      .subscribe(hero => this.hero = hero);
  }

  save(): void {
    this.heroService.update(this.hero)
      .then(() => this.goBack());
  }

  goBack(): void {
    this.location.back();
  }
}

results matching ""

    No results matching ""