第八章 多用户与路由守卫

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

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

1. 概述

我们需要这么一个机制

  1. 建立一种召唤师机制,让每个英雄有自己的召唤师,召唤师可以控制多个英雄
  2. 做好隐私保护工作:每个召唤师只能更改自己的英雄信息
  3. 非召唤师是不能进入某些页面的
(1)验证用户账户流程

一个完整的验证流程如下:

  1. 存储要访问的URL
  2. 根据本地的用户已登录标识判断用户是否已登录,如果已登录就放行,如果未登录,转入登录页面
  3. 在登录页面中,用户输入用户名后系统根据填入的用户名在用户表中查找,若没找到,报错
  4. 在用户输入密码后判断密码与用户名是否匹配,若不匹配,报错
  5. 若匹配,存储已登录标识至本地,导航到原本要访问的URL即第一步中存储的URL,删掉本地存储的URL

看上去我们需要实现以下三个Service

  • UserService:里面有对对象User所有的查找select,新增create,删除delete,修改update操作
  • AuthService:里面有所有用于用户认证相关的方法,其中有些需要利用到UserService中的方法
  • AuthGuard:路由拦截器,用于对目标路由进行拦截后通过AuthService来看此用户是否有权限访问该路由,根据判断结果导航到不同路径
流程图:

(2)路由守卫

Angular提供了路由守卫机制来处理以下需求:

  • 判断该用户是否有权导航到目标组件
  • 在显示目标组件前,我们可能得先得到某些数据
  • 在离开组件前,我们可能要先保存修改,或放弃更改 ...

我们可以在路由那一章中的app-routing.module.ts中给一些需要路由守卫的地方添加守卫:

  • CanActivate能处理导航到某路由的情况,这也是作为本章范例的路由
  • CanActivate能处理导航到某子路由的情况
  • CanDeactivate能处理从当前路由离开的情况
  • Resoulve能在路由激活之前获取路由数据

在路由树(分层路由)的每一层上,我们都能设置一些路由守卫。路由器会先按照从子路由到根路由的顺序来检查CanDeactivate守护条件,然后会按照从根路由到子路由的顺序检查CanActivate条件,如果任何 守卫返回dalse,其它尚未完成的守卫会被取消,这样整个导航就被取消了。

路由守卫图示

2. 增加召唤师角色

(1) 修改domain文件

我们需要给英雄们添加召唤师id(ownerId)这一属性来对应每个召唤师:

src/app/domain/hero.ts
export class Hero {
    id: number;
    name: string;
    gender: string;
    desc:string;
    ownerId: number;
}

我们还需要有对应的召唤师对象,在domain中新建user.ts对象文件

src/app/domain/user.ts
export class User {
  id: number;
  username: string;
  password: string;
  name: string;
}
(2) 修改json文件

相信大家在前面已经学会了用json模拟数据库的方法,我们修改一下data.json文件,首先人为的给已有英雄添加ownerId属性,表示这一位英雄属于哪个召唤师。然后我们再在data.json中加上user对象的json表现形式:

src/app/data.json
{
  "heroes": [
    {
      "id": 1,
      "name": "The Monkey King",
      "gender":"MALE",
      "desc": "孙悟空",
      "ownerId" :"1"
    },
    {
      "id": 2,
      "name": "Monk Pig",
      "gender":"MALE",
      "desc": "猪八戒",
      "ownerId" :"1"
    },
    {
      "id": 3,
      "name": "Friar Sand",
      "gender":"MALE",
      "desc": "沙和尚",
      "ownerId" :"1"
    },
    {
      "id": 4,
      "name": "Harry Potter",
      "gender":"MALE",
      "desc": "哈利·波特",
      "ownerId" :"2"
    },
    {
      "id": 5,
      "name": "Ron Weasley",
      "gender":"MALE",
      "desc": "罗恩·韦斯莱",
      "ownerId" :"2"
    },
    {
      "id": 6,
      "name": "Hermione Granger",
      "gender":"FEMALE",
      "desc": "赫敏·格兰杰",
      "ownerId" :"2"
    }
  ],
  "users":[
    {
      "id":1,
      "username": "user1",
      "password": "123456",
      "name": "The Tang Monk"
    },
    {
      "id":2,
      "username": "user2",
      "password": "123456",
      "name": "Albus Dumbledore"
    }
  ]
}

我们首先要先实现路由守卫(不该看的地方不能看)以及登录功能,所以就先在json文件里模拟了一条user数据,需要注意的是这些数据都是明文,既简陋也不安全,具体的安全问题会在以后的章节讲述,在这里我们先不管它。

3. 增加核心模块core

下面我们来吧与认证(Auth)相关的代码组织在一个新的模块下,这个模块可以随便命名,我们将其命名为core,

使用 ng g m core 命令,结果会在src/app下新建一个core目录,然后在core目录下新建一个core.module.ts。

src/app/core/core.module.ts(v1)
import { NgModule, ModuleWithProviders, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
  imports: [
    CommonModule
  ]
})
export class CoreModule {
  constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
    if (parentModule) {
      throw new Error(
        'CoreModule is already loaded.');
    }
  }
}

有可能有这么一个服务,它会在网页的很多部分中被引用,但我们不希望它被创建多次。也有可能某些只应用于AppComponent模板的一次性组件,没必要共享它们,然而把它们放在根模块下显得太乱。我们就可以把这样的服务放在core模块下,然后通过AppModule导入CoreModule获得服务。

以上代码我们只在启动时导入它一次,注意到在constructor构造器中我们会把一个类型为CoreModule的parentModule注入自身,这看起来像是一个危险的循环,但@SkipSelf装饰器可以在当前注入器所有祖先注入器中寻找CoreModule。如果该构造函数在我们所期望的AppModule中运行,就没有任何祖先注入器能够提供CoreModule的实例,于是注入器会放弃查找。默认情况下,当注入器找不到想找的提供商时,会抛出一个错误。 但@Optional装饰器表示找不到该服务也无所谓。 于是注入器会返回null,parentModule参数也就被赋成了空值,而构造函数没有任何异常。

别忘了在app.module.ts中导入core模块
src/app/app.module.ts
  import { CoreModule } from './core/core.module';
  ...
    imports: [
      ...
      CoreModule,
      ...
    ],
  ...

4. 增加一个导航栏组件 NavbarComponent

我们希望用户未登录的时候不能访问自己的个人中心,并且导航到登录页面,那么就需要用到CanActivate。 先增加一个导航栏组件 NavBarComponent:

ng g c navbar

在导航栏组件NavbarComponent.html下添加一个路由到个人中心的button。

src/app/navbar/navbar.component.html
<nav>
  <a routerLink="/heroes" routerLinkActive="active" id="Heroes">Heroes</a>
  <a routerLink="/profile" routerLinkActive="active" id="Profile">Profile</a>
</nav>

<div>
  <router-outlet></router-outlet>
</div>

src/app/navbar/navbar.component.css


/* Navigation link styles */

nav a {
  padding: 5px 10px;
  text-decoration: none;
  margin-right: 10px;
  margin-top: 10px;
  display: inline-block;
  background-color: #eee;
  border-radius: 4px;
}

nav a:visited,
a:link {
  color: #607D8B;
}

nav a:hover {
  color: #039be5;
  background-color: #CFD8DC;
}

nav a.active {
  color: #039be5;
}

修改 src/app/app.component.html ,增加navbar

src/app/app.component.html(v1)
<h1 class="title">Hello My Heroes</h1>
<app-navbar></app-navbar>
<router-outlet></router-outlet>

5. 个人中心 profile

接下来,新建一个个人中心模块ProfileModule,别忘记在AppModule中引入ProfileModule:

ng g m profile

修改 app.module.ts ,增加 profile.module

src/app/app.module.ts (v1)
import { ProfileModule } from './profile/profile.module';
  imports: [
    ProfileModule
  ],

我们还需要与Profile模块配套的组件profile.component,输入:

ng g c profile

建好组件之后我们还需要一个profile-routing.module来控制profile内部的路由,在profile目录下新建profile-routing.module.ts文件:

src/app/profile/profile-routing.module.ts (v1)
//import angular core
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
//import my components
import { ProfileComponent } from './profile.component'; 
const routes: Routes = [
    {
        path: '',
        component: ProfileComponent
    }
];

@NgModule({
    imports: [RouterModule.forChild(routes)],
    exports: [RouterModule]
})

export class ProfileRoutingModule { }

别忘记把profile-routing模块引入profile.module中。

src/app/profile/profile.module.ts (v1)
//import angular core
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule }    from '@angular/forms';
//import my modules
import { ProfileRoutingModule } from './profile-routing.module';
//import my components
import { ProfileComponent } from './profile.component';

@NgModule({
  imports: [
    CommonModule,
    ProfileRoutingModule,
    FormsModule
  ],
  declarations: [
    ProfileComponent
  ]
})
export class ProfileModule { }

6. 增加路由守卫

增加一个auth-guard服务

ng g s service/auth-guard

来新建AuthGuardService,注意angular-cli对于Camel写法的文件名是才用-来分割每个首字母要大写的单词

下面在app-routing.module.ts中填入这些组件和服务

src/app/app-routing.module.ts(v1)
  //import angular core
  import { NgModule } from '@angular/core';
  import { RouterModule, Routes } from '@angular/router';
  //import my mudules
  import { HeroesModule } from './heroes/heroes.module';
  import { ProfileModule } from './profile/profile.module';
  //import my components
  //import my service
  import { AuthGuardService } from './service/auth-guard.service';
  const routes: Routes = [
      {
          path: '',
          redirectTo: '/heroes',
          pathMatch: 'full'
      },
      {
          path: 'profile',
          canActivate: [AuthGuardService],
          loadChildren: 'app/profile/profile.module#ProfileModule'
      }
  ];

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

  export class AppRoutingModule { }

下面我们实现AuthGuardService

src/app/service/auth-guard.service.ts
import { Injectable, Inject } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

@Injectable()
export class AuthGuardService implements CanActivate {

  constructor(private router: Router) { }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    let url: string = state.url;
    return this.checkLogin(url);
  }
  checkLogin(url: string): boolean {
    //已登录
    if (localStorage.getItem('userId') !== null) {
      //返回true,放行
      return true;
    }
    //未登录
    else {
      //首先将要访问的URL暂存
      localStorage.setItem('redirectUrl', url);
      //然后导航到登录页面
      this.router.navigate(['/login']);
      //返回false,取消本次导航
      return false;
    }
  }
}

我们注意上面代码中

(localStorage.getItem('userId') !== null)

这里使用检测本地是否存储userId来判断用户是否登录,这是一个漏洞百出的实现,本例中先这样使用,而在实际的项目开发中,一定要选用其它实现方式或者搭配后端代码来提升安全性。

7. 增加 AuthService

下面我们还需要一个为登录功能服务的service,来判断用户名密码是否输入正确,如果正确那就把登录状态写入localStorage。以后也会用它来为注册功能进行服务 在命令行或终端中输入:

ng g s service/auth

打开auth.service.ts

src/app/service/auth.service.ts
import { Injectable, Inject } from '@angular/core';
import { Http, Headers, Response } from '@angular/http';

import 'rxjs/add/operator/toPromise';
import { Auth } from '../domain/auth';

@Injectable()
export class AuthService {

  constructor(private http: Http, @Inject('user') private userService) { }

  loginWithCredentials(username: string, password: string): Promise<Auth> {
    return this.userService.getUserByUsername(username)  //调用UserService中的方法来查找user
      .then(user => {
        let auth = new Auth();
        localStorage.removeItem('userId');  //首先移除当前本地存储的userId
        let redirectUrl = (localStorage.getItem('redirectUrl') === null) ?
          '/' : localStorage.getItem('redirectUrl');
        auth.redirectUrl = redirectUrl;      //存储原本要访问的Url
        if (null === user) {
          //没找到user
          auth.hasError = true;
          auth.errMsg = 'user not found';
        } else if (password === user.password) {
          //找到user并与密码匹配成功
          auth.user = Object.assign({}, user);
          auth.hasError = false;
          localStorage.setItem('userId', user.id);
        } else {
          //密码错误
          auth.hasError = true;
          auth.errMsg = 'password not match';
        }
        return auth;
      })
      .catch(this.handleError);
  }
  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error);
    return Promise.reject(error.message || error);
  }
}

我们注意到这里面用到了一个新的对象Auth与新的服务UserService Auth对象:

  • 我们需要存储用户最初要导航到的页面URL
  • 用户对象
  • 如果发生错误,存储错误信息

所以我们还需要声明Auth对象:

src/app/domain/auth.ts
import { User } from './user';

export class Auth {
    user: User;
    hasError: boolean;
    errMsg: string;
    redirectUrl: string;
  }

8. 增加 UserService

我们还需要实现UserService,在终端或命令行中输入:

ng g s service/user
src/app/service/user.service.ts
import { Injectable } from '@angular/core';

import { Http, Headers, Response } from '@angular/http';

import 'rxjs/add/operator/toPromise';
import { User } from '../domain/user';
import { ApiService } from './api.service';


@Injectable()
export class UserService {

  private api_url : string ;
  private headers : Headers ;

  constructor(private http: Http, private apiService: ApiService) {
      this.api_url = apiService.getUrl() + '/users';
      this.headers = apiService.getHeaders();
  }

  //查询所有User
  getUsers(): Promise<User[]> {
    const url = `${this.api_url}`;
    return this.http.get(url, { headers: this.headers })
      .toPromise()
      .then(res => res.json() as User[])
      .catch(this.handleError);
  }

  //按id查询User
  getUserById(id: number): Promise<User> {
    const url = `${this.api_url}/${id}`;
    return this.http.get(url, { headers: this.headers })
      .toPromise()
      .then(res => res.json() as User)
      .catch(this.handleError);
  }

  //按username查询User
  getUserByUsername(username: string): Promise<User> {
    const url = `${this.api_url}/?username=${username}`;
    return this.http.get(url, { headers: this.headers })
      .toPromise()
      .then(res => {
        let users = res.json() as User[];
        return (users.length > 0) ? users[0] : null;
      })
      .catch(this.handleError);
  }

  //创建一个User
  createUser(user: User): Promise<User> {
    const url = `${this.api_url}`;
    return this.http
      .post(url, JSON.stringify(user), { headers: this.headers })
      .toPromise()
      .then(res => res.json() as User)
      .catch(this.handleError);
  }

  //修改某个User
  updateUser(user: User): Promise<User> {
    const url = `${this.api_url}/${user.id}`;
    return this.http
      .put(url, JSON.stringify(user), { headers: this.headers })
      .toPromise()
      .then(() => user)
      .catch(this.handleError);
  }

  //删除某个User
  deleteUser(user: User): Promise<void> {
    const url = `${this.api_url}/${user.id}`;
    return this.http.delete(url, { headers: this.headers })
      .toPromise()
      .then(() => null)
      .catch(this.handleError);
  }

  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error);
    return Promise.reject(error.message || error);
  }
}

以上UserService已经实现了查找所有User,按id查找某个User,按username查找某个User,还有插,改,删的操作,在这里我们先只用了按username查找某个User的方法

9. 增加 login 组件

下面我们来实现login组件 依旧在命令行或终端中输入:

ng g c login
src/app/login/login.component.html
<br>
<div>
  <form #formRef="ngForm" (ngSubmit)="onSubmit(formRef.value)">
    <fieldset ngModelGroup="login">
      <div>
        <label class ="mark">Username: </label>
        <input name="username" type="text" [(ngModel)]="username" #usernameRef="ngModel" required minlength="3">
        <label *ngIf="usernameRef.errors?.required">this is required</label>
        <label *ngIf="usernameRef.errors?.minlength">should be at least 3 charactors</label>
        <label *ngIf="auth?.hasError">{{auth.errMsg}}</label>
      </div>
      <div>
        <label class ="mark">Password: </label>
        <input name="password" type="password" [(ngModel)]="password" #passwordRef="ngModel" required>
        <label *ngIf="passwordRef.errors?.required">this is required</label>
      </div>
      <button type="submit">Login</button>
    </fieldset>
  </form>
</div>
src/app/login/login.component.ts
import { Component, OnInit, Inject } from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';

import { Auth } from '../domain/auth';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  username = '';
  password = '';
  auth: Auth;

  constructor(@Inject('auth') private service, private router: Router) { }

  ngOnInit() {
  }

  onSubmit(formValue){
    this.service
      .loginWithCredentials(formValue.login.username, formValue.login.password)
      .then(auth => {
        let redirectUrl = (auth.redirectUrl === null)? '/': auth.redirectUrl;
        if(!auth.hasError){
          this.router.navigate([redirectUrl]);
          localStorage.removeItem('redirectUrl');
          location.reload();
        } else {
          this.auth = Object.assign({}, auth);
        }
      });
  }
}
src/app/login/login.component.css
label {
  display: inline-block;
  width: 10em;
  margin: .5em 0;
  color: #607D8B;
  font-weight: bold;
}

.mark{
    display: inline-block;
    width: 6em;
    margin: .5em 0;
    color: #607D8B;
    font-weight: bold;
}
input {
  height: 2em;
  font-size: 1em;
  padding-left: .4em;
}

button {
  margin-top: 20px;
  font-family: Arial;
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
  cursor: hand;
}

button:hover {
  background-color: #cfd8dc;
}

button:disabled {
  background-color: #eee;
  color: #ccc;
  cursor: auto;
}

在导航栏组件中加入login按钮:

src/app/navbar/navbar.component.html
<nav>
  <a routerLink="/heroes" routerLinkActive="active" id="Heroes">Heroes</a>
  <a routerLink="/profile" routerLinkActive="active" id="Profile">Profile</a>
  <a routerLink="/login" routerLinkActive="active" id="Login">Login</a>
</nav>

<div>
  <router-outlet></router-outlet>
</div>

在core模块中声明我们所需要的服务

src/app/core/core.module.ts(v2)
//import angular core
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';

//import service
import { AuthService } from '../service/auth.service';
import { UserService } from '../service/user.service';
import { AuthGuardService } from '../service/auth-guard.service';

@NgModule({
    imports: [
        CommonModule
    ],
    providers: [
        { provide: 'auth', useClass: AuthService },
        { provide: 'user', useClass: UserService },
        AuthGuardService
    ]
})
export class CoreModule {
    constructor( @Optional() @SkipSelf() parentModule: CoreModule) {
        if (parentModule) {
            throw new Error(
                'CoreModule is already loaded.');
        }
    }
}

给app-routing.module.ts添加上login路由组件

app-routing.module.ts(v2)
//import angular core
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
//import my mudules
import { HeroesModule } from './heroes/heroes.module';
import { ProfileModule } from './profile/profile.module';
//import my components
import { LoginComponent } from './login/login.component';
//import my service
import { AuthGuardService } from './service/auth-guard.service';

const routes: Routes = [
    {
        path: '',
        redirectTo: '/heroes',
        pathMatch: 'full'
    },
    {
        path: 'profile',
        canActivate: [AuthGuardService],
        loadChildren: 'app/profile/profile.module#ProfileModule'
    },
    {
        path: 'login',
        component: LoginComponent
    }

];

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

export class AppRoutingModule { }

在有关登录的最后,我们再来检查一下app.module.ts,看看有没有哪些模块忘记导入了

src/app/app.module.ts
//import angular core
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { FormsModule }    from '@angular/forms';
import { CoreModule } from './core/core.module';
import { ProfileModule } from './profile/profile.module';
//import my modules
import { AppRoutingModule } from './app-routing.module';
import { HeroesModule } from './heroes/heroes.module';
//import my components
import { AppComponent } from './app.component';
import { NavbarComponent } from './navbar/navbar.component';
import { LoginComponent } from './login/login.component';
//import my service
import { ApiService } from './service/api.service';

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

现在启动json server,在命令行或终端中输入:

json-server data.json

观察网页,点击profile按钮,会自动跳入到登录页面,输入用户名:user1,密码:123456 后成功路由到了profile页面,现在大体功能已经实现,稍后我们会对页面进行美化

10. 有登录就有注销

现在需要分清两种存储方式,一种是基于本地的localStorage存储,也就是我们当前用的,还有一种是基于窗口的sessioStorage存储

  • localStorage:除非你调用方法移除,否则它就会永远存在你的电脑里(等会我们就要实现这个方法)
  • sessionStorage:基于窗口,当窗口关闭时,存储在它里面的数据全部删除 当然还有更加完善的存储方式,也可以自定义,这就要视情况而定了 (到底用哪一种,就看个人喜好和项目要求啦)

给app.component.html添加注销按钮

src/app/navbar/navbar.component.html(v2)
<nav>
  <a routerLink="/hero" routerLinkActive="active" id="Hero">Hero</a>
  <a routerLink="/profile" routerLinkActive="active" id="Profile">Profile</a>
  <a routerLink="/login" routerLinkActive="active" id="Login">Login</a>
  <button (click)="logout()" id="Logout">Logout</button>
</nav>

<div>
  <router-outlet></router-outlet>
</div>

实现其功能

src/app/navbar/navbar.component.ts
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {

  userId: number;

  constructor() { }

  ngOnInit(){
    const divLogin = document.getElementById('Login');
    const divLogout = document.getElementById('Logout');
    this.userId = -1;
    if(localStorage.getItem('userId') !== null){
     //已登录
     divLogin.style.display = 'none';

     this.userId = <number><any>localStorage.getItem('userId');

    }
    else{
      //未登录
      divLogout.style.display = 'none';
    }
  }

  logout() {
    localStorage.removeItem('userId');
    this.userId = -1;
    location.reload();
  }
}

这里稍稍做了一些调整:已登录就不显示Login按钮而只显示Logout,未登录反之

注销还是蛮简单的,只需要把本地存储的验证去掉即可。

11. 注册

现在能登录是json文件中有一个现成的user,我们不能只靠手动操作数据库来增加用户,一定要实现用户自主注册的功能。

本例的注册方式十分简单:直接用UserService向json文件中写入User信息。但一般项目中不会被用到,因为安全性极差,用户名和密码都是明文存的,这种网站黑客都懒得攻击,现实项目中大家一定不能用这种方式,更为具体的方法在以后的章节会有所涉及。

首先新建注册Register组件,在命令行或终端中输入:

ng g c register

并在app-routing.module.ts导入它:

src/app/app-routing.module.ts(v3)
//import angular core
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
//import my mudules
import { HeroModule } from './hero/hero.module';
import { ProfileModule } from './profile/profile.module';
//import my components
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
//import my service
import { AuthGuardService } from './service/auth-guard.service';

const routes: Routes = [
    {
        path: '',
        redirectTo: '/heroes',
        pathMatch: 'full'
    },
    {
        path: 'profile',
        canActivate: [AuthGuardService],
        loadChildren: 'app/profile/profile.module#ProfileModule'
    },
    {
        path: 'login',
        component: LoginComponent
    },
    {
        path: 'register',
        component: RegisterComponent
    }    
];

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

export class AppRoutingModule { }

下面我们完善一下注册的组件:

src/app/register.component.html
<div>
  <h2>Register</h2>
  <div>
    <label class="mark">username: </label>
    <input [(ngModel)]="user.username" placeholder="username" />
  </div>
  <div>
    <label class="mark">password: </label>
    <input type="password" [(ngModel)]="user.password" placeholder="password" />
  </div>
  <button (click)="onSubmit()">Submit</button>
</div>
src/app/register.component.ts
import { Component, OnInit } from '@angular/core';

import { User } from '../domain/user';

import { UserService } from '../service/user.service';

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.css'],
  providers: [UserService]
})
export class RegisterComponent implements OnInit {

  user= new User();
  id: number;

  constructor(private userService: UserService) { }

  ngOnInit() {

  }

  onSubmit(){
    this.userService.createUser(this.user)
    .then(user => {
      console.log(user);
    });
  }
}
src/app/register.component.css
label {
    display: inline-block;
    width: 6em;
    margin: .5em 0;
    color: #607D8B;
    font-weight: bold;
  }

  .mark {
    display: inline-block;
    width: 6em;
    margin: .5em 0;
    color: #607D8B;
    font-weight: bold;
  }

  input {
    height: 2em;
    font-size: 1em;
    padding-left: .4em;
  }

  button {
    margin-top: 20px;
    font-family: Arial;
    background-color: #eee;
    border: none;
    padding: 5px 10px;
    border-radius: 4px;
    cursor: pointer;
    cursor: hand;
  }

  button:hover {
    background-color: #cfd8dc;
  }

  button:disabled {
    background-color: #eee;
    color: #ccc;
    cursor: auto;
  }

然后我们在NavbarComponent.html中添加到注册的路由:

src/app/navbar/navbar.component.html(v3)
<nav>
  <a routerLink="/hero" routerLinkActive="active" id="Hero">Hero</a>
  <a routerLink="/profile" routerLinkActive="active" id="Profile">Profile</a>
  <a routerLink="/login" routerLinkActive="active" id="Login">Login</a>
  <a routerLink="/register" routerLinkActive="active" id="Register">Register</a>
  <a (click)="logout()" id="Logout">Logout</a>
</nav>

<div>
  <router-outlet></router-outlet>
</div>

最后来看一下navbar.component.ts的最终情况:在登录时取消显示注册,未登录时显示:

src/app/navbar/navbar.component.ts
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {

  userId: number;

  constructor() { }

  ngOnInit(){
    const divLogin = document.getElementById('Login');
    const divLogout = document.getElementById('Logout');
    const divRegister = document.getElementById('Register');
    this.userId = -1;
    if(localStorage.getItem('userId') !== null){
     //已登录
     divLogin.style.display = 'none';
     divRegister.style.display='none';

     this.userId = <number><any>localStorage.getItem('userId');

    }
    else{
      //未登录
      divLogout.style.display = 'none';
    }
  }

  logout() {
    localStorage.removeItem('userId');
    this.userId = -1;
    location.reload();
  }
}

12. 修改个人信息

下面我们要可以修改自己的个人信息,首先,增加个人信息组件 profile-detail 。

ng g c profile/profile-detail

在profile-routing.module.ts中填入这三个组件的路由信息

//import angular core
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
//import my components
import { ProfileComponent } from './profile.component';
import { ProfileDetailComponent } from './profile-detail/profile-detail.component';

const routes: Routes = [
    {
        path: '',
        component: ProfileComponent,
        children: [
            {
                path:'',
                component: ProfileDetailComponent
            },
            {
                path:'profile-detail',
                component: ProfileDetailComponent
            }
        ]
    }
];

@NgModule({
    imports: [RouterModule.forChild(routes)],
    exports: [RouterModule]
})

export class ProfileRoutingModule { }

在profile.component.html中加入通向profile-detail与my-hero-list的路由连接:

src/app/profile/profile.component.html
<nav>
  <a routerLink="/profile-detail" routerLinkActive="active" id="ProfileDetail">ProfileDetail</a>
</nav>

<div>
  <router-outlet></router-outlet>
</div>

并给它添上样式:

src/app/profile/profile.component.css
nav a {
  padding: 5px 10px;
  text-decoration: none;
  margin-right: 10px;
  margin-top: 10px;
  display: inline-block;
  background-color: #eee;
  border-radius: 4px;
}

nav a:visited,
a:link {
  color: #607D8B;
}

nav a:hover {
  color: #039be5;
  background-color: #CFD8DC;
}

nav a.active {
  color: #039be5;
}

以下是给 profile-detail 组件写代码。

src/app/profile/profile-detail/profile-detail.component.html
<div *ngIf="user">
  <h3> ID: {{user.id}} </h3>
  <h3> Username: {{user.username}} </h3>
  <h3> Name: {{user.name}} </h3>
  <div class = "items">
    <label>Name:</label>
    <input [(ngModel)]="user.name" placeholder="Name" />
  </div>
  <button (click)="goBack()">Back</button>
  <button (click)="save()">Save</button>
</div>
src/app/profile/profile-detail/profile-detail.component.css
label {
  display: inline-block;
  width: 3em;
  margin: .5em 0;
  color: #607D8B;
  font-weight: bold;
}

input {
  height: 2em;
  font-size: 1em;
  padding-left: .4em;
}

button {
  margin-top: 20px;
  font-family: Arial;
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
  cursor: hand;
}

button:hover {
  background-color: #cfd8dc;
}

button:disabled {
  background-color: #eee;
  color: #ccc;
  cursor: auto;
}
src/app/profile/profile-detail/profile-detail.component.ts
//import angular core
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';

//import third package
import 'rxjs/add/operator/switchMap';

//import service
import { UserService } from '../../service/user.service';

//import class
import { User } from '../../domain/user';

@Component({
  selector: 'app-profile-detail',
  templateUrl: './profile-detail.component.html',
  styleUrls: ['./profile-detail.component.css'],
  providers: [UserService]
})
export class ProfileDetailComponent implements OnInit {

  user: User;

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private location: Location,
    private userService: UserService) { }

  ngOnInit() {
    this.userService.getUserById(<number><any>localStorage.getItem('userId'))
      .then(user => this.user = user);
  }

  save(): void{
    this.userService.updateUser(this.user)
      .then(() => this.goBack());
  }

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

再检查一下profile.module.ts中有没有添加FormsModule:

src/app/profile/profile.module.ts
//import angular core
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule }    from '@angular/forms';
//import my modules
import { ProfileRoutingModule } from './profile-routing.module';
//import my components
import { ProfileComponent } from './profile.component';
import { ProfileDetailComponent } from './profile-detail/profile-detail.component';

@NgModule({
  imports: [
    CommonModule,
    ProfileRoutingModule,
    FormsModule
  ],
  declarations: [
    ProfileComponent,
    ProfileDetailComponent,
  ]
})
export class ProfileModule { }

13. 访问我的英雄

最后目标:每个召唤师,可以查看所有的英雄列表,可以新增英雄,新增英雄的主人就是这个召唤师。但是每个召唤师只能修改自己的英雄,包括查看自己的英雄详情,修改自己的英雄和删除自己的英雄。

首先分别在终端输入

ng g c heroes
ng g c heroes/hero-search
ng g c heroes/my-hero-list
ng g c heroes/my-hero-detail

来分别代表总的英雄组件、英雄查询组件、自己的英雄列表和自己的某个英雄的详细信息这四个组件。这四个组件会自动注册进 src/app/heroes/heroes.module.ts 当中。

在heroes-routing.module.ts中填入这四个组件的路由信息,并修改原来的路由为子路由形式。

//import angular core
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
//import my components
import { HeroesComponent } from './heroes.component';
import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { MyHeroListComponent } from './my-hero-list/my-hero-list.component';
import { MyHeroDetailComponent} from './my-hero-detail/my-hero-detail.component';

const routes: Routes = [
    {
        path: '',
        component: HeroesComponent,
        children: [
            { 
                path: '', 
                component: HeroListComponent 
            },
            {   path: 'hero/:id', 
                component: HeroDetailComponent 
            },
            {
                path:'myheroes',
                component: MyHeroListComponent
            },
            {
                path:'myhero/:id',
                component: MyHeroDetailComponent
            }
        ]
    }
];

@NgModule({
    imports: [RouterModule.forChild(routes)],
    exports: [RouterModule]
})

export class HeroesRoutingModule { }

在heroes.component.html中加入通向myheroes的路由连接:

src/app/heroes/heroes.component.html
<nav>
  <a routerLink="/heroes" routerLinkActive="active" id="heroes">All Heroes</a>
  <a routerLink="/myheroes" routerLinkActive="active" id="myheroes">My Heroes</a>
</nav>

<div>
  <router-outlet></router-outlet>
</div>

并给它添上样式:

src/app/heroes/heroes.component.css
nav a {
  padding: 5px 10px;
  text-decoration: none;
  margin-right: 10px;
  margin-top: 10px;
  display: inline-block;
  background-color: #eee;
  border-radius: 4px;
}

nav a:visited,
a:link {
  color: #607D8B;
}

nav a:hover {
  color: #039be5;
  background-color: #CFD8DC;
}

nav a.active {
  color: #039be5;
}

以下是分别为 my-hero-list 和 my-hero-detail 编写代码。我们需要把原来原来heroes模块下有关新建与修改的功能移到这里来,因为只有召唤师可以召唤修改自己的英雄,而别的召唤师则只能查看别人的英雄,因为相关知识在先前章节都已讲过,这里就不做更详细的说明了,直接列出两个组件所需要的代码:

下面是 my-hero-list

src/app/heroes/my-hero-list/my-hero-list.component.html
<h2>My Heroes</h2>
<div>
  <label>Hero name:</label> <input #heroName />
  <button (click)="addHero(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)="deleteHero(hero); $event.stopPropagation()">x</button>
  </li>
</ul>
src/app/heroes/my-hero-list/my-hero-list.component.css
.selected {
  background-color: #CFD8DC !important;
  color: white;
}
.heroes {
  margin: 0 0 2em 0;
  list-style-type: none;
  padding: 0;
  width: 15em;
}
.heroes li {
  cursor: pointer;
  position: relative;
  left: 0;
  background-color: #EEE;
  margin: .5em;
  padding: .3em 0;
  height: 1.6em;
  border-radius: 4px;
}
.heroes li:hover {
  color: #607D8B;
  background-color: #DDD;
  left: .1em;
}
.heroes li.selected:hover {
  background-color: #BBD8DC !important;
  color: white;
}
.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;
}
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;
}
src/app/heroes/my-hero-list/my-hero-list.component.ts
//import angualr core
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';

//import third package
import 'rxjs/add/operator/switchMap';

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

//import class
import { Hero } from '../../domain/hero';

@Component({
  selector: 'app-my-hero-list',
  templateUrl: './my-hero-list.component.html',
  styleUrls: ['./my-hero-list.component.css'],
  providers: [HeroService]
})
export class MyHeroListComponent implements OnInit {

  myId: number;
  heroes: Hero[];
  selectedHero: Hero;

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

  ngOnInit() {
    this.myId = <number><any>localStorage.getItem('userId');
    this.heroService.getHeroesByOwnerId(this.myId)
      .then(heroes => this.heroes = heroes);
  }

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

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

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
    this.router.navigate(['/myhero', this.selectedHero.id]);
  }
}

下面是 my-hero-detail

src/app/heroes/my-hero-detail/my-hero-detail.component.html
<div *ngIf="hero">
  <h2>{{hero.id}}. {{hero.name}}</h2>
  <div class = "items">
    <label>Name:</label>
    <input [(ngModel)]="hero.name" placeholder="Name" />
  </div>
  <div class = "items">
    <label>Gender:</label>
    <select *ngIf="heroGenders" [(ngModel)]="hero.gender" name = "gender" placeholder="gender">
      <option *ngFor="let heroGender of heroGenders ">{{heroGender}}</option>
    </select>
  </div>
  <div class = "items">
    <label>Desc:</label>
    <input [(ngModel)]="hero.desc" placeholder="Description" />
  </div>
  <button (click)="goBack()">Back</button>
  <button (click)="save()">Save</button>
</div>
src/app/heroes/my-hero-detail/my-hero-detail.component.css
label {
  display: inline-block;
  width: 4em;
  margin: .5em 0;
  color: #607D8B;
  font-weight: bold;
}
input {
  height: 2em;
  font-size: 1em;
  padding-left: .4em;
}
button {
  margin-top: 20px;
  font-family: Arial;
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer; cursor: hand;
}
button:hover {
  background-color: #cfd8dc;
}
button:disabled {
  background-color: #eee;
  color: #ccc; 
  cursor: auto;
}
src/app/heroes/my-hero-detail/my-hero-detail.component.ts
//import angular core
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';

//import third package
import 'rxjs/add/operator/switchMap';

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

//import class
import { Hero } from '../../domain/hero';

@Component({
  selector: 'app-my-hero-detail',
  templateUrl: './my-hero-detail.component.html',
  styleUrls: ['./my-hero-detail.component.css'],
  providers: [HeroService]
})
export class MyHeroDetailComponent implements OnInit {

  hero: Hero;
  heroGenders: string[] = ['MALE', 'FEMALE', 'OTHER'];

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private heroService: HeroService,
    private location: Location) { }

  ngOnInit(): void {
    this.route.params
      .switchMap((params: Params) => this.heroService.getHeroById(+params['id']))
      .subscribe(hero => this.hero = hero);
  }

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

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

然后删除原hero模块下所有有关新建英雄修改英雄的组件,方法:

src/app/hero/hero-list/hero-list.component.html
<h2>All Heroes</h2>
<app-hero-search></app-hero-search>
<ul class="heroes">
  <li *ngFor="let hero of heroes"
    [class.selected]="isSelected(hero)"
    (click)="onSelect(hero)">
    <span class="badge">{{ hero.id }}</span> {{ hero.name }}
  </li>
</ul>
src/app/hero/hero-list/hero-list.component.ts
//import angular core
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';

//import third package
import 'rxjs/add/operator/switchMap';

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

//import class
import { Hero } from '../../domain/hero';

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

  heroes: Hero[];
  selectedId : number;

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

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

  isSelected(hero: Hero){
    return hero.id === this.selectedId;
  }

  onSelect(hero: Hero){
    this.router.navigate(['/hero',hero.id]);
  }
}
src/app/hero/hero-detail/hero-detail.component.html
<div *ngIf="hero">
  <h3>{{ hero.id }}.{{ hero.name }}</h3>
  <div class="items">
    <label>Id: </label>{{ hero.id }}
  </div>
  <div class="items">
    <label>Name: </label>{{ hero.name }}
  </div>
  <div class="items">
    <label>Gender: </label>{{ hero.gender }}
  </div>
  <div class="items">
    <label>Desc: </label>{{ hero.desc }}
  </div>
  <div class="items" *ngIf="user">
    <label>Owner: </label>{{ user.name }}
  </div>
</div>
<div>
  <button (click)="goBack()">Back</button>
</div>
src/app/hero/hero-detail/hero-detail.component.ts
//import angular core
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';

//import third package
import 'rxjs/add/operator/switchMap';

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

//import class
import { Hero } from '../../domain/hero';
import { User } from '../../domain/user';

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

  hero: Hero;
  user: User;

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private heroService: HeroService,
    private userService: UserService,
    private location: Location) { }

  ngOnInit(): void {
    this.route.params
      .switchMap((params: Params) => this.heroService.getHeroById(+params['id']))
      .subscribe(hero => {
        this.hero = hero;
        this.userService.getUserById(hero.ownerId)
        .then(user => this.user = user);
      });
  }
  goBack(): void {
    this.location.back();
  }
}

下面是为新增的 hero-search 组件写的代码。

src/app/heroes/hero-search/hero-search.component.ts
import { Component, OnInit } from '@angular/core';
import { Router }            from '@angular/router';

import { Observable }        from 'rxjs/Observable';
import { Subject }           from 'rxjs/Subject';

// Observable class extensions
import 'rxjs/add/observable/of';

// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';

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

@Component({
  selector: 'app-hero-search',
  templateUrl: './hero-search.component.html',
  styleUrls: [ './hero-search.component.css' ],
  providers: [HeroService]
})
export class HeroSearchComponent implements OnInit {
  heroes: Observable<Hero[]>;
  private searchTerms = new Subject<string>();

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

  // Push a search term into the observable stream.
  search(term: string): void {
    this.searchTerms.next(term);
  }

  ngOnInit(): void {
    this.heroes = this.searchTerms
      .debounceTime(300)        // wait 300ms after each keystroke before considering the term
      .distinctUntilChanged()   // ignore if next search term is same as previous
      .switchMap(term => term   // switch to new observable each time the term changes
        // return the http search observable
        ? this.heroService.searchHero(term)
        // or the observable of empty heroes if there was no search term
        : Observable.of<Hero[]>([]))
      .catch(error => {
        // TODO: add real error handling
        console.log(error);
        return Observable.of<Hero[]>([]);
      });
  }

  gotoDetail(hero: Hero): void {
    let link = ['/hero', hero.id];
    this.router.navigate(link);
  }
}
src/app/heroes/hero-search/hero-search.component.html
<div id="search-component">
  <h4>Hero Search</h4>
  <input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
  <div>
    <div *ngFor="let hero of heroes | async"
         (click)="gotoDetail(hero)" class="search-result" >
      {{hero.name}}
    </div>
  </div>
</div>
src/app/heroes/hero-search/hero-search.component.css
  .search-result{
    border-bottom: 1px solid gray;
    border-left: 1px solid gray;
    border-right: 1px solid gray;
    width:195px;
    height: 16px;
    padding: 5px;
    background-color: white;
    cursor: pointer;
  }

  .search-result:hover {
    color: #eee;
    background-color: #607D8B;
  }

  #search-box{
    width: 200px;
    height: 20px;
  }

因为有每个英雄都有了一个召唤师id(ownerId)的属性,我们需要一个按召唤师id查询英雄的方法,在新建英雄的时候,作为必填项,不仅要传入英雄的名字,还要传入英雄的召唤师id: 现在的hero.service.ts变成了这样:

src/app/service/hero.service.ts
//import angular core
import { Injectable } from '@angular/core';
import { Http, Headers, Response } from '@angular/http';

//import third package
import { Observable }     from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';

//import my services
import { ApiService } from './api.service';

//import class
import { Hero } from '../domain/hero';

@Injectable()
export class HeroService {
  private api_url : string ;
  private headers : Headers ;

  constructor(private http: Http, private apiService: ApiService) {
      this.api_url = apiService.getUrl() + '/heroes';
      this.headers = apiService.getHeaders();
  }

  //查询所有Hero
  getHeroes(): Promise<Hero[]> {
    const url = `${this.api_url}`;
    return this.http.get(url, { headers: this.headers })
      .toPromise()
      .then(res => res.json() as Hero[])
      .catch(this.handleError);
  }

  //按id查询Hero
  getHeroById(id: number): Promise<Hero> {
    const url = `${this.api_url}/${id}`;
    return this.http.get(url, { headers: this.headers })
      .toPromise()
      .then(res => res.json() as Hero)
      .catch(this.handleError);
  }

  //按ownerId查询Hero
  getHeroesByOwnerId(ownerId: number): Promise<Hero[]> {
    const url = `${this.api_url}/?ownerId=${ownerId}`;
    return this.http.get(url, { headers: this.headers })
      .toPromise()
      .then(res => res.json() as Hero[])
      .catch(this.handleError);
  }

  //search hero
  searchHero(term: string): Observable<Hero[]> {
    return this.http
      .get(`${this.api_url}?name_like=${term}`)
      .map(response => response.json() as Hero[]);
  }

  //新建Hero
  createHero(hero: Hero): Promise<Hero> {
    const url = `${this.api_url}`;
    return this.http
      .post(url, JSON.stringify(hero), { headers: this.headers })
      .toPromise()
      .then(res => res.json() as Hero)
      .catch(this.handleError);
  }

  //按name与userId新建Hero
  createHeroByNameUserId(name: string, ownerId: number): Promise<Hero> {
    let hero = {
      name: name,
      ownerId: ownerId
    }
    const url = `${this.api_url}`;
    return this.http
      .post(url, JSON.stringify(hero), { headers: this.headers })
      .toPromise()
      .then(res => res.json() as Hero)
      .catch(this.handleError);
  }

  //修改Hero
  updateHero(hero: Hero): Promise<Hero> {
    const url = `${this.api_url}/${hero.id}`;
    return this.http
      .put(url, JSON.stringify(hero), { headers: this.headers })
      .toPromise()
      .then(res => res.json() as Hero)
      .catch(this.handleError);
  }

  //删除某个Hero
  deleteHero(hero: Hero): Promise<void> {
    const url = `${this.api_url}/${hero.id}`;
    return this.http.delete(url, { headers: this.headers })
      .toPromise()
      .then(() => null)
      .catch(this.handleError);
  }

  //按id删除某个Hero
  deleteHeroById(id: number): Promise<void> {
    const url = `${this.api_url}/${id}`;
    return this.http.delete(url, { headers: this.headers })
      .toPromise()
      .then(() => null)
      .catch(this.handleError);
  }

  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error);
    return Promise.reject(error.message || error);
  }
}

至此,我们的召唤师终于可以为所欲为的召唤他们的英雄们了。

results matching ""

    No results matching ""