网站首页 » 前端开发 » Angular 2+ » Angular 4.x HTTP 示例
上一篇:
下一篇:

Angular 4.x HTTP 示例

启动服务

ng serve --open

上面的命令行就是启动服务并运行 应用。这个命令行有几个作用:1.启动应用服务;2.监听文件变化。3.重建APP。

提供 HTTP Services

HttpModule 并不是核心的 NgModule。HttpModule 是一个可选项。你可以引入这个包@angular/http。

您可以从@ angular / http导入,因为systemjs.config配置了SystemJS,以便在需要时加载该库。

注册 HTTP services

应用将依赖于Angular 的 http 服务,而 http 服务又依赖于其他服务。@angular/http 库中的 HttpModule 可以为我们提供一整套 HTTP 服务。

为了让这些服务可以在APP 中使用,我们把需要在 AppModule 的 import 列表中添加 HttpModule。

src/app/app.module.ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { HttpModule }    from '@angular/http';
 
import { AppRoutingModule } from './app-routing.module';
 
import { AppComponent }         from './app.component';
import { DashboardComponent }   from './dashboard.component';
import { HeroesComponent }      from './heroes.component';
import { HeroDetailComponent }  from './hero-detail.component';
import { HeroService }          from './hero.service';
 
@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    AppRoutingModule
  ],
  declarations: [
    AppComponent,
    DashboardComponent,
    HeroDetailComponent,
    HeroesComponent,
  ],
  providers: [ HeroService ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

记得在 AppModule 里的 imports 数组中也要添加 HttpModule。

模拟Web API

我们建议您在根AppModule提供程序中注册全局服务。

在你能够处理 hero 数据请求的Web服务器之前,HTTP 客户端将从 mock 服务(内存中的Web API)中获取并保存数据。

更新 src/app/app.module.ts 文件

src/app/app.module.ts (v2)
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { HttpModule }    from '@angular/http';
 
import { AppRoutingModule } from './app-routing.module';
 
// Imports for loading & configuring the in-memory web api
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService }  from './in-memory-data.service';
 
import { AppComponent }         from './app.component';
import { DashboardComponent }   from './dashboard.component';
import { HeroesComponent }      from './heroes.component';
import { HeroDetailComponent }  from './hero-detail.component';
import { HeroService }          from './hero.service';
 
@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    InMemoryWebApiModule.forRoot(InMemoryDataService),
    AppRoutingModule
  ],
  declarations: [
    AppComponent,
    DashboardComponent,
    HeroDetailComponent,
    HeroesComponent,
  ],
  providers: [ HeroService ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

该示例不需要真正的API服务器,而是通过将InMemoryWebApiModule添加到模块导入来模拟与远程服务器的通信,有效地替换Http客户端的XHR后端服务,并使用内存替代方案。

InMemoryWebApiModule.forRoot(InMemoryDataService),

forRoot()配置方法使用一个InMemoryDataService类来引导内存数据库。新建 in-memory-data.service.ts 文件内容如下 :

src/app/in-memory-data.service.ts
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
  createDb() {
    const heroes = [
      { id: 0,  name: 'Zero' },
      { 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' }
    ];
    return {heroes};
  }
}

用此文件替换 mock-heroes.ts (上面的 in-memory-data.service.ts 文件新建好后,这个文件就可以删除了)。 增加英雄“零”来确认数据服务可以处理一个id = 0的英雄。

Heroes 和 HTTP

在当前的HeroService实现中,能过 Promise resolve 来返回模拟英雄数据。

src/app/hero.service.ts (old getHeroes)
getHeroes(): Promise<Hero[]> {
  return Promise.resolve(HEROES);
}

为了预期效果最终用 HTTP 客户端获取英雄,并且这是一个异步操作。

把 getHeroes() 转化为 HTTP

src/app/hero.service.ts (updated getHeroes and new class members)
private heroesUrl = 'api/heroes';  // URL to web api
 
constructor(private http: Http) { }
 
getHeroes(): Promise<Hero[]> {
  return this.http.get(this.heroesUrl)
             .toPromise()
             .then(response => response.json().data as Hero[])
             .catch(this.handleError);
}
 
private handleError(error: any): Promise<any> {
  console.error('An error occurred', error); // for demo purposes only
  return Promise.reject(error.message || error);
}

更新 import 声明

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

import 'rxjs/add/operator/toPromise';

import { Hero } from './hero';

HTTP Promise

Angular http.get 会返回一个RxJS Observable。Observable 是管理异步数据流的有效方式。 您将在本页后面阅读有关Observables的信息。

现在,您已经使用 toPromise 将 Observable转换为Promise。

.toPromise()

这里有很多用于扩展 Observable 功能的操作符(比如:toPromise)。要想使用这些功能,你必需添加这个操作符,你可以像下面这样从 RxJS 库中import 进来。

import 'rxjs/add/operator/toPromise';

回调中获取数据

在 Promise 的 then() 回调中,通过 HTTP 的 response 调用 json() 方法来获取数据。

.then(response => response.json().data as Hero[])

响应的 JSON 有一个数据属性,它保存了调用者所需的英雄数组。 所以你抓住该数组并作为 Promise 值返回它就可以了。

调用者不知道你从服务器中获取了数据。它还是像以前一样收到这个 heroes Promise 。

错误处理

在 getHeroes() 方法的最后面,要捕获取服务失败信息,并把它们传给错误处理器。

.catch(this.handleError);

这是关键的一步。 您必须预期 HTTP 故障,因为它们会经常发生,并且也不在你的掌控之中。

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

通过 id 获取 hero

当 HeroDetailComponent 请求 HeroService 来获取一个英雄时,HeroService 获取所有的英雄后通过 id 过滤得到目标 hero 。 这只是一个模拟,但一个真正的服务器,一般都不会获取所有的 heroes。 大多数 Web API 都支持api / hero /:id(如api / hero / 11)形式 (get-by-id) 来请求单个 hero。

更新 HeroService.getHero() 方法

src/app/hero.service.ts
getHero(id: number): Promise<Hero> {
  const url = `${this.heroesUrl}/${id}`;
  return this.http.get(url)
    .toPromise()
    .then(response => response.json().data as Hero)
    .catch(this.handleError);
}

这个请求方法跟 getHeroes() 很像,hero id 用于过滤 hero。

并且返回的数据是单个hero ,而不是一个 hero 数组。

更新 getHeroes API

尽管您对 getHeroes() 和 getHero() 进行了重大的内部更改,但公共签名没有更改。 您仍然从两种方法返回承诺。 您不必更新任何调用它们的组件。

现在是时候添加创建删除功能了。

更新 hero details

尝试在英雄详细视图中编辑英雄的名字。 当您键入时,英雄名称将在视图标题中更新。 但如果您单击后退按钮,更改将丢失。

更新没有丢失。 当应用程序使用模拟英雄列表时,有什么变化?更新应用程序直接应用于单个应用程序范围的共享列表中的 hero 对象。 现在您正在从服务器获取数据,如果要更改持久性,则必须将其写回服务器。

保存 hero details 功能

在 hero detail 模板的最后面添加一个保存按钮,并绑定 save() 方法

src/app/hero-detail.component.html (save)
<button (click)="save()">Save</button>

添加保存方法,这个方法通过服务的 update() 方法将数据永久保存在服务端,并作返回上一面的操作。

src/app/hero-detail.component.ts (save)
save(): void {
  this.heroService.update(this.hero)
    .then(() => this.goBack());
}

给 hero 服务添加 update() 方法

update() 方法的整体结构类似于getHeroes()方法,但它使用HTTP put() 来维护服务器端更改。

src/app/hero.service.ts (update)
private headers = new Headers({'Content-Type': 'application/json'});

update(hero: Hero): Promise<Hero> {
  const url = `${this.heroesUrl}/${hero.id}`;
  return this.http
    .put(url, JSON.stringify(hero), {headers: this.headers})
    .toPromise()
    .then(() => hero)
    .catch(this.handleError);
}

为了让服务器知道要更新哪一个 hero ,我们在 url 中添加了 hero id。put() 方法中是一个 hero 的JSON 字符串编码。application/json 是一个请求头标识。

刷新浏览器,修改 hero 名,保存修改,点击浏览器的返回按钮,修改后的数据依然有效。

新增 添加 heroes 功能

要想添加英雄,得需要给 hero 一个名字。你可以通过 input 配置添加按钮。

heroes component 模板中添加如下代码:

src/app/heroes.component.html (add)
<div>
  <label>Hero name:</label> <input #heroName />
  <button (click)="add(heroName.value); heroName.value=''">
    Add
  </button>
</div>

响应点击事件,调用组件的点击处理程序,然后清除输入字段,以便再次添加 hero 。

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

当 hero 名不为空的时候,会在服务端创建一个 hero ,然后将新的英雄添加到数组。

在HeroService 类中实现 create() 方法

src/app/hero.service.ts (create)
create(name: string): Promise<Hero> {
  return this.http
    .post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers})
    .toPromise()
    .then(res => res.json().data as Hero)
    .catch(this.handleError);
}

新增 删除 hero 功能

在视图中的每一个 hero 都应该有一个删除按钮。

在 heroes component 模板的最后面添加删除按钮,在hero name 之后,在</li> 结束标签之前。

<button class="delete"
  (click)="delete(hero); $event.stopPropagation()">x</button>

<li> 标签内容如下:

src/app/heroes.component.html (li-element)
<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>

除了调用组件的 delete() 方法之外,删除按钮的点击处理程序代码要阻止点击事件的冒泡 – 因为你不希望按下删除按钮后也触发<li>点击处理程序,因为这样做会选择用户将会 删除。

delete() 的逻辑代码如下:

src/app/heroes.component.ts (delete)
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; }
      });
}

添加删除按钮样式

src/app/heroes.component.css (additions)
button.delete {
  float:right;
  margin-top: 2px;
  margin-right: .8em;
  background-color: gray !important;
  color:white;
}

新增 Hero 服务 delete() 方法

添加删除方法,此方法可以从服务器中删除数据

src/app/hero.service.ts (delete)
delete(id: number): Promise<void> {
  const url = `${this.heroesUrl}/${id}`;
  return this.http.delete(url, {headers: this.headers})
    .toPromise()
    .then(() => null)
    .catch(this.handleError);
}

Observables

每个Http服务方法返回一个 Observables 的 HTTP 响应对象。

HeroService将该Observable转换为Promise并将其转发给调用者。 本节将介绍如何,何时以及为什么直接返回Observable。

背景

Observables 是一个事件流,你可以使用类似数组的操作符进行处理。

Angular 具有对 Observables  的基本支持。 开发人员通过 RxJS 库中的运算符和扩展扩充了该支持。 很快你就可以看到。

回想一下,HeroService 将 toPromise 操作符链接到 http.get() 的 Observable 结果。 该操作符将 Observable 转换为Promise,并将该 Promise 传递给调用者。

转化为 Promise 往往是一个不错的选择。 您通常要求 http.ge() 获取单个数据块。 收到数据后,就完成了。 调用组件可以容易地以 Promise 的形式处理单个结果。

但请求并不总是只做一次。 您可以启动一个请求,取消它,并在服务器响应第一个请求之前发出不同的请求。

请求 – 取消 – 新请求序列难以使用 Promises 实现,但使用 Observables 可轻松实现。

添加名字搜索功能

你将要添加一个 hero 搜索功能到“英雄之旅”。 当用户在搜索框中键入 hero 名字时,将会搜索此字符串。

首先创建将搜索查询发送到服务器的Web API的HeroSearchService。

src/app/hero-search.service.ts
import { Injectable } from '@angular/core';
import { Http }       from '@angular/http';
 
import { Observable }     from 'rxjs/Observable';
import 'rxjs/add/operator/map';
 
import { Hero }           from './hero';
 
@Injectable()
export class HeroSearchService {
 
  constructor(private http: Http) {}
 
  search(term: string): Observable<Hero[]> {
    return this.http
               .get(`api/heroes/?name=${term}`)
               .map(response => response.json().data as Hero[]);
  }
}

HeroSearchService 中的 http.get() 调用类似于 HeroService 中的 http.get() 调用,尽管 URL 现在多了查询的字符串参数。

更重要的是,你不再需要调用 Promise() 。 相反,将链接到另一个 RxJS 运算符map() 后,从 http.get() 返回 Observable ,以从响应数据中提取的 hero。 RxJS 操作符链使得响应处理变得简单易读。

HeroSearchComponent

添加一个 HeroSearchComponent 用来调用新方法 HeroSearchService

组件的模板非常简单

src/app/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/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;
}

当用户在搜索框中键入内容时,keyup 事件绑定将更新搜索框中的值并调用组件的 search() 方法。

如预期的那样,* ngFor从组件的 heroes 属性遍历出每一个 hero 对象。

但是正如你很快会看到的,heroes 属性现在是一个 Observable 的 hero 数组,而不仅仅是一个 hero 数组。 * ngFor在通过异步管道(AsyncPipe)路由之前,无法对 Observable 进行任何操作。 异步管道预订了Observable,并生成了一系列 hero 到 * ngFor。

创建 HeroSearchComponent  和 元数据

src/app/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 { HeroSearchService } from './hero-search.service';
import { Hero } from './hero';
 
@Component({
  selector: 'hero-search',
  templateUrl: './hero-search.component.html',
  styleUrls: [ './hero-search.component.css' ],
  providers: [HeroSearchService]
})
export class HeroSearchComponent implements OnInit {
  heroes: Observable<Hero[]>;
  private searchTerms = new Subject<string>();
 
  constructor(
    private heroSearchService: HeroSearchService,
    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.heroSearchService.search(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 = ['/detail', hero.id];
    this.router.navigate(link);
  }
}

搜索字词

private searchTerms = new Subject<string>();

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

Subject 是一个事件流的生成者; searchTerms 产生 Observable 字符串,作为名字搜索的过滤条件。

对 search() 的每次调用通过调用next() 将一个新的字符串放入 Subject 的 Observable  流中。

初始化 heroes 属性

Subject  也是一个 Observable 。 您可以将搜索词流转换成 hreo 数组,并将结果保存到 hero 属性。

heroes: Observable<Hero[]>;
 
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.heroSearchService.search(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[]>([]);
    });
}

Import RxJS 运算符

大多数 RxJS 运算符不包括在 Angular 基础的 Observable 实现中。 基本实现只包括 Angular 本身所需要的。

当您需要更多RxJS 功能时,通过导入定义它们的库来扩展 Observable。 以下是该组件需要的所有 RxJS 导入:

src/app/hero-search.component.ts (rxjs imports)
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';

把搜索添加到 dashboard

在 DashboardComponent  模板的最后面添加搜索 HTML

src/app/dashboard.component.html
<h3>Top Heroes</h3>
<div class="grid grid-pad">
  <a *ngFor="let hero of heroes"  [routerLink]="['/detail', hero.id]"  class="col-1-4">
    <div class="module hero">
      <h4>{{hero.name}}</h4>
    </div>
  </a>
</div>
<hero-search></hero-search>

最后,从 hero-search.component.ts 中导入HeroSearchComponent ,并把它添加到 declarations 数组中。

src/app/app.module.ts (search)
declarations: [
  AppComponent,
  DashboardComponent,
  HeroDetailComponent,
  HeroesComponent,
  HeroSearchComponent
],

再次运行应用程序在 Dashboard 中,在搜索框中输入一些文本。 如果您输入的字符匹配任何现有的 hero 名字,你会看到这样的东西:

Angular 2 HTTP

注意:

上面的项目中是基于 Angualr CLI 的,所以默认情况下是没有 angular-in-memory-web-api 的,你需要通过 nmp 来安装它,在项目根目录运行cmd:

npm install --save angular-in-memory-web-api

安装完这个库后,就可以使用 angular-in-memory-web-api。

源代码

app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { DashboardComponent } from './dashboard.component';
import { HeroesComponent } from './heroes.component';
import { HeroDetailComponent } from './hero-detail.component';

const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'dashboard', component: DashboardComponent },
  { path: 'detail/:id', component: HeroDetailComponent },
  { path: 'heroes', component: HeroesComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; // <-- NgModel lives here
import { HttpModule } from '@angular/http';

import { AppRoutingModule } from './app-routing.module';

// Imports for loading & configuring the in-memory web api
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';

import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroesComponent } from './heroes.component';
import { DashboardComponent } from './dashboard.component';
import { HeroService } from './hero.service';
import { HeroSearchComponent } from './hero-search.component';


// 用于定义模块用的装饰器
@NgModule({
  // 导入模块依赖的组件、指令等;
  declarations: [
    AppComponent,
    HeroesComponent,
    HeroDetailComponent,
    DashboardComponent,
    HeroSearchComponent
  ],
  // 用来导入当前模块所需的其他模块;
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    InMemoryWebApiModule.forRoot(InMemoryDataService),
    AppRoutingModule
  ],

  // 导入相关的服务
  providers: [HeroService],
  // 标记出引导组件,在 Angular 启动应用时,将被标记的组件渲染到模板中。
  bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})

export class AppComponent {
  title = 'Tour of Heroes';
}
app.component.html
<h1>{{title}}</h1>
<nav>
  <a routerLink="/dashboard">Dashboard</a>
  <a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
app.component.css
h1 {
  font-size: 1.2em;
  color: #999;
  margin-bottom: 0;
}

h2 {
  font-size: 2em;
  margin-top: 0;
  padding-top: 0;
}

nav a {
  padding: 5px 10px;
  text-decoration: none;
  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;
}
dashboard.component.ts
import { Component, OnInit } from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';

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

  heroes: Hero[] = [];

  constructor(private heroService: HeroService) { }

  ngOnInit(): void {
    this.heroService.getHeroes()
      .then(heroes => this.heroes = heroes.slice(1, 5));
  }
}
dashboard.component.html
<h3>Top Heroes</h3>
<div class="grid grid-pad">
  <a *ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]" class="col-1-4">
    <div class="module hero">
      <h4>{{hero.name}}</h4>
    </div>
  </a>
</div>
<hero-search></hero-search>
dashboard.component.css
[class*='col-'] {
  float: left;
  padding-right: 20px;
  padding-bottom: 20px;
}

[class*='col-']:last-of-type {
  padding-right: 0;
}

a {
  text-decoration: none;
}

*,
*:after,
*:before {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

h3 {
  text-align: center;
  margin-bottom: 0;
}

h4 {
  position: relative;
}

.grid {
  margin: 0;
}

.col-1-4 {
  width: 25%;
}

.module {
  padding: 20px;
  text-align: center;
  color: #eee;
  max-height: 120px;
  min-width: 120px;
  background-color: #607D8B;
  border-radius: 2px;
}

.module:hover {
  background-color: #EEE;
  cursor: pointer;
  color: #607d8b;
}

.grid-pad {
  padding: 10px 0;
}

.grid-pad>[class*='col-']:last-of-type {
  padding-right: 20px;
}

@media (max-width: 600px) {
  .module {
    font-size: 10px;
    max-height: 75px;
  }
}

@media (max-width: 1024px) {
  .grid {
    margin: 0;
  }
  .module {
    min-width: 60px;
  }
}
heroes.component.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Hero } from './hero';
import { HeroService } from './hero.service';

// 创建一个HEROES 常量(数组类型,数组的每一项为一个 Hero 实例子)
const 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' }
];

@Component({
  selector: 'my-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css'],
  //声明服务,当前组件及子组件可用共用此服务实例
  providers: [HeroService]
})

export class HeroesComponent implements OnInit {

  heroes: Hero[];

  selectedHero: Hero;

  // 注入服务
  constructor(
    private heroService: HeroService,
    private router: Router,
  ) { };
  // 获取数据方法定义
  getHeroes(): void {
    this.heroService.getHeroes().then(heroes => this.heroes = heroes);
  }
  //选中处理函数
  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
  // 通过生命周期勾子调用函数获取数据
  ngOnInit(): void {
    this.getHeroes();
  }

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

  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; }
      });
  }
}
heroes.component.html
<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)">
    <span class="badge">{{hero.id}}</span>
    <span> {{hero.name}}</span>

    <button class="delete" (click)="delete(hero); $event.stopPropagation()">x</button>
  </li>
</ul>
<hero-detail [hero]="selectedHero"></hero-detail>
<div *ngIf="selectedHero">
  <h2>
    {{selectedHero.name | uppercase}} is my hero
  </h2>
  <button (click)="gotoDetail()">View Details</button>
</div>
<div>
  <label>Hero name:</label> <input #heroName />
  <button (click)="add(heroName.value); heroName.value=''">
    Add
  </button>
</div>
heroes.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;
}
hero-detail.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { Hero } from './hero';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Location } from '@angular/common';
import { HeroService } from './hero.service';
//用于提取参数
import 'rxjs/add/operator/switchMap';

@Component({
    selector: 'hero-detail',
    templateUrl: './hero-detail.component.html',
    styleUrls: ['./hero-detail.component.css']
})

export class HeroDetailComponent implements OnInit {
    @Input() 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);
    }

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

    save(): void {
        this.heroService.update(this.hero)
            .then(() => this.goBack());
    }
}
hero-detail.component.html
<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>
  <button (click)="goBack()">Back</button>
  <button (click)="save()">Save</button>
</div>
hero-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;
}
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 { HeroSearchService } from './hero-search.service';
import { Hero } from './hero';

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

  constructor(
    private heroSearchService: HeroSearchService,
    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.heroSearchService.search(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 = ['/detail', hero.id];
    this.router.navigate(link);
  }
}
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>
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;
}
hero-search.service.ts
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

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

import { Hero } from './hero';

@Injectable()
export class HeroSearchService {

  constructor(private http: Http) { }

  search(term: string): Observable<Hero[]> {
    return this.http
      .get(`api/heroes/?name=${term}`)
      .map(response => response.json().data as Hero[]);
  }
}
hero.service.ts
import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';

import 'rxjs/add/operator/toPromise';

import { Hero } from './hero';

@Injectable()
export class HeroService {
    private heroesUrl = 'api/heroes';  // URL to web api
    private headers = new Headers({ 'Content-Type': 'application/json' });

    constructor(private http: Http) { }


    getHeroes(): Promise<Hero[]> {
        return this.http.get(this.heroesUrl)
            .toPromise()
            .then(response => response.json().data as Hero[])
            .catch(this.handleError);
    }
    getHero(id: number): Promise<Hero> {
        const url = `${this.heroesUrl}/${id}`;
        return this.http.get(url)
            .toPromise()
            .then(response => response.json().data as Hero)
            .catch(this.handleError);
    }

    update(hero: Hero): Promise<Hero> {
        const url = `${this.heroesUrl}/${hero.id}`;
        return this.http
            .put(url, JSON.stringify(hero), { headers: this.headers })
            .toPromise()
            .then(() => hero)
            .catch(this.handleError);
    }

    create(name: string): Promise<Hero> {
        return this.http
            .post(this.heroesUrl, JSON.stringify({ name: name }), { headers: this.headers })
            .toPromise()
            .then(res => res.json().data as Hero)
            .catch(this.handleError);
    }

    delete(id: number): Promise<void> {
        const url = `${this.heroesUrl}/${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); // for demo purposes only
        return Promise.reject(error.message || error);
    }
}
hero.ts
export class Hero {
    id: number;
    name: string;
}
in-memory-data.service.ts
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
  createDb() {
    const heroes = [
      { id: 0, name: 'Zero' },
      { 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' }
    ];
    return { heroes };
  }
}

 

  • 微信扫一扫,赏我

  • 支付宝扫一扫,赏我

声明

原创文章,不经本站同意,不得以任何形式转载,如有不便,请多多包涵!

本文永久链接:http://yunkus.com/angular-tutorial-part6/

Leave a Reply

Your email address will not be published. Required fields are marked *

评论 END