kittencup / angular2-ama-cn

angular2 随便问
691 stars 101 forks source link

HTTP #42

Open kittencup opened 8 years ago

kittencup commented 8 years ago

该issue关闭讨论,如有问题请去 https://github.com/kittencup/angular2-ama-cn/issues/43 提问

目录

kittencup commented 8 years ago

介绍

Angular 2 有它自己的HTTP库,我们可以用它来调用外部API。

当我们调用外部服务器时,我们希望我们的用户仍然能够与页面进行交互。也就是说,我们不希望我们的页面冻结直到从外部服务器的HTTP请求返回。为了达到这种效果,我们的HTTP请求是异步的。

异步处理从历史上看,比处理同步码更麻烦。在Javascript中,一般有三种方法来处理异步代码:

在Angualr 2,处理异步代码的首选方法是使用Observables,所以我们将在本章覆盖。

有一整章的RxJS和Observable:在这一章中我们将使用Observables并没有太多解释。如果你是刚开始读这本书的这一章,你应该知道,在上一章有更详细的介绍。

在本章中,我们将要:

本章中完整示例代码可以在示例代码中的HTTP文件夹中找到。该文件夹包含README.md,是构建和运行项目的说明。

在阅读本章的时候,试着在代码中运行代码,并自由发挥,以获得关于它的所有作品的更深入的了解。

kittencup commented 8 years ago

使用angular2 http

HTTP在Angular 2中已经分解成一个独立的模块。这意味着要使用它,你需要导入angular2/http模块,例如

import { Http, Response, RequestOptions, Headers } from 'angular2/http'; 

从 angular2/http 导入 ——---------

在我们的app.ts中,我们导入HTTP_BINGDINGS,这是一个模块的便利集合。

# code/http/app/ts/app.ts

import {Component, bootstrap, View} from "angular2/angular2";
import {HTTP_BINDINGS} from "angular2/http";

当我们引导我们的应用程序,我们将添加HTTP_PROVIDERS作为一个依赖。其效果是,我们将能够注入HTTP(和其他几个模块)到我们的组件。

bootstrap(HttpApp, [HTTP_PROVIDERS]);

现在 我们就可以注入Http服务到我们的组件里(在任何地方,实际上也可以使用DI)

class MyFooComponent {
    constructor(public http: Http) {}
    makeRequest(): void {
        // do something with this.http ...
    }
}
kittencup commented 8 years ago

一个基本的请求

我们要做的第一件事是向jsonplaceholder API创建一个简单的GET请求的

我们要做的是:

下面是我们的例子中的截图:

image

创建SimpleHTTPComponent @Component

我们要做的第一件事就是导入几个模块,然后为我们@Component指定一个selector

# code/http/app/ts/components/SimpleHTTPComponent.ts

import {Component, View, NgIf} from "angular2/angular2";
import {Http, Response} from "angular2/http";

@Component({
  selector: "simple-http"
})

创建SimpleHTTPComponent 模板

下一步创建我们的视图

# code/http/app/ts/components/SimpleHTTPComponent.ts

@Component({
  selector: 'simple-http',
  template: `
  <h2>Basic Request</h2>
  <button type="button" (click)="makeRequest()">Make Request</button>
  <div *ngIf="loading">loading...</div>
  <pre>{{data | json}}</pre>
`

首先我们指定一个我们用到的NgIf指令

我们的模板有三个有趣的部分:

在button上 我们绑定一个(click)来调用在我们控制器里的makeRequest函数,我们将立即定义这个函数

我们要告诉用户我们的请求在加载中,所以使用ng-if如果实例变量loading是true的话显示loading

data是一个对象,比较好的调试对象的方式是我们在这里使用的JSON pipe。我们在这里使用pre标签,易于我们阅读的格式。

创建SimpleHTTPComponent控制器

我们开始为我们的SimpleHTTPComponent定义一个新的类

# code/http/app/ts/components/SimpleHTTPComponent.ts

export class SimpleHTTPComponent {
    data: Object;
    loading: boolean;

我们有2个实例变量:data和loading, 这将被分别用于我们的API返回值和loading指示器。

接下去我们定义construct:

# code/http/app/ts/components/SimpleHTTPComponent.ts

constructor(public http: Http) {}

constructor内容为空,但我们注入一个模块:Http

记住当我们在public http: Http使用public关键字,TypeScript将会将http分配给this.http,这是一个缩写:

// other instance variables here
http: Http;

constructor(http:Http){
    this.http = http;
}

现在,让我们实现makeRequest函数,来创建我们的第一个HTTP请求:

# code/http/app/ts/components/SimpleHTTPComponent.ts

makeRequest(): void {
    this.loading = true;
    this.http.request("http://jsonplaceholder.typicode.com/posts/1")
      .toRx()
      .subscribe((res: Response) => {
        this.data = res.json();
        this.loading = false;
      });
}

当我们调用makeRequest,第一件事我们会设置this.loading = true,这将打开我们的视图中的loading指示器。

创建一个HTTP请求很简单:我们向创建一个GET请求。我们调用this.http.request并传入URL

http.request 返回Observable。我们可以使用subscribe来订阅这个变化

# code/http/app/ts/components/SimpleHTTPComponent.ts

.subscribe((res: Response) => {
        this.data = res.json();
        this.loading = false;
});

当我们http.request返回,这个流会发色一个Response对象,我们通过使用json提取响应的主体对象,然后我们设置this.data为该对象。

因为我们有一个响应,我们不需要在加载了,所以我们设置this.loading = false

.subscribe还可以处理failures,分别通过传递函数到流的第二和第三个参数。在生产应用程序,这将是一个好主意来处理这些情况了。也就是说,如果请求失败(即流发出错误)this.loading也应设置为false。

完整的SimpleHTTPComponent

下面是我们的SimpleHTTPComponent完全样子:

# code/http/app/ts/components/SimpleHTTPComponent.ts

/*
 * Angular
 */
import {Component} from 'angular2/core';
import {Http, Response} from 'angular2/http';

@Component({
  selector: 'simple-http',
  template: `
  <h2>Basic Request</h2>
  <button type="button" (click)="makeRequest()">Make Request</button>
  <div *ngIf="loading">loading...</div>
  <pre>{{data | json}}</pre>
`
})
export class SimpleHTTPComponent {
  data: Object;
  loading: boolean;

  constructor(public http: Http) {
  }

  makeRequest(): void {
    this.loading = true;
    this.http.request('http://jsonplaceholder.typicode.com/posts/1')
      .subscribe((res: Response) => {
        this.data = res.json();
        this.loading = false;
      });
  }
}
kittencup commented 8 years ago

编写一个YouTubeSearchComponent

最后一个例子是一个简单的方式来获得一个API Serve的数据到你的代码。现在,让我们尝试建立一个更复杂的例子。

在本节中,我们要建立一个方法来搜索YouTube作为你的类型。当搜索返回时,我们将显示一个视频缩略图的结果列表,以及每个视频的描述和链接。

下面是一个截图当我搜索“cats playing ipads”:

image

在这个例子中,我们打算写几件事情:

让我们一次来处理每一个部分

Patrick Stapleton 有一个很棒的名为angular2-webpack-starter资源。次资源有一个RxJS例子,用来自动完成Github上的库。在这一节一些想法是从这个例子启发的。这是有很多奇妙的例子项目你应该看看。

编写SearchResult

首先,让我们开始写一个基本的SearchResult类。这个类只是一个方便用来存储从我们的搜索结果中感兴趣的特定字段

# code/http/app/ts/components/YouTubeSearchComponent.ts

class SearchResult {
  id: string;
  title: string;
  description: string;
  thumbnailUrl: string;
  videoUrl: string;

  constructor(obj?: any) {
    this.id              = obj && obj.id             || null;
    this.title           = obj && obj.title          || null;
    this.description     = obj && obj.description    || null;
    this.thumbnailUrl    = obj && obj.thumbnailUrl   || null;
    this.videoUrl        = obj && obj.videoUrl       || `https://www.youtube.com/watch?v=${this.id}`;
  }
}

采取obj?:any这种模式,让我们模拟关键字参数。我们的想法是,我们可以创建一个新的SearchResult,并只是传递一个包含我们要指定键的对象。

唯一在这里指出的是,我们用了一个硬编码的URL格式构建videoUrl。欢迎使用更多参数的函数来改变这个功能,如果你需要的话,可以直接在视图中使用id来构建这个URL。

编写 YouTubeService

API

这个例子中我们将要使用YouTube V3搜索API

为了使用这个API,你需要有一个API key. 在示例代码中我已包含了API key给你使用,然而,当你读到本书时,你可能会发现它的速度限制。如果发生这种情况,你需要发行你自己的key。

要发行你自己的key 请查看这个文档,为简单起见,我已经注册了一个server key,如果你打算把你的javascript代码在线使用,你可能应该使用一个browser key。

我们建立2个常量为我们的YouTubeService映射我们API key和API URL

let YOUTUBE_API_KEY: string = "XXX_YOUR_KEY_HERE_XXX";
let YOUTUBE_API_URL: string = "https://www.googleapis.com/youtube/v3/search";

最终 我们要测试我们的应用程序。当我们测试时,我们发现的一个问题是,我们并不总是想要测试生产,我们通常要测试的分期或开发的API。

为了帮助这种环境的配置,我们可以做的事情之一是让这些常量注入。

为什么我们要注入这些常量,而不是让它们以正常方式使用?因为如果我们让他们注入,我们可以

通过注入这些值,对于这些值我们有很多灵活性

为了使这些值注射,我们使用 provide(...,{useValue:....}) 语法,是这样的:

# code/http/app/ts/components/YouTubeSearchComponent.ts

export var youTubeServiceInjectables: Array<any> = [
  provide(YouTubeService,{useClass:YouTubeService}),
  provide(YOUTUBE_API_KEY,{useValue:YOUTUBE_API_KEY}),
  provide(YOUTUBE_API_URL,{useValue:YOUTUBE_API_URL})
];

在这里我们指定我们要绑定的 YOUTUBE_API_KEY injectably 到YOUTUBE_API_KEY的值(对于YOUTUBE_API_URL,和我们接下去要定义的YouTubeService是同样的)

如果你还记得,在我们的应用程序中提供一些可供选择的东西,我们需要使它成为bootstrap的依赖,因此我们在这export youTubeServiceInjectables,在我们的app.ts我们就可以import它

// http/app.ts
import {youTubeServiceInjectables} from "components/YouTubeSearchComponent"; // ....
// further down
bootstrap(HttpApp, [HTTP_PROVIDERS, youTubeServiceInjectables]);

现在我们可以注入YOUTUBE_API_KEY代替直接使用变量。

YouTubeService Constructor

我们创建我们的YouTubeService,并使用注解 @Injectable:

# code/http/app/ts/components/YouTubeSearchComponent.ts
@Injectable()
export class YouTubeService {
  constructor(public http: Http,
              @Inject(YOUTUBE_API_KEY) private apiKey: string,
              @Inject(YOUTUBE_API_URL) private apiUrl: string) {
  }

在construct我们注入3个东西:

注意我们也为3个参数创建了3个实例变量,这意味着我们可以从this.http,this.apiKey和this.apiUrl来分别访问它们

注意,我们使用@Inject(YOUTUBE_API_KEY)明确地注入。

YouTubeService 搜索

接下去 来实现search函数,search需要一个query字符串,并返回一个Observable,它发射出一个 SearchResult[]的流,也就是说发射每一个SearchResults数组

# code/http/app/ts/components/YouTubeSearchComponent.ts

search(query: string): Rx.Observable<SearchResult[]> {
    let params: string = [
      `q=${query}`,
      `key=${this.apiKey}`,
      `part=snippet`,
      `type=video`,
      `maxResults=10`
    ].join("&");
    let queryUrl: string = `${this.apiUrl}?${params}`;

我们手动的方式建立queryUrl。首先我们简单地将每个query参数放到params变量内(你可以通过阅读API文档搜索找到每个参数的意义)

然后,我们通过连接apiUrl和params建立了queryUrl。

现在我们有一个queryUrl,我们可以创建一个请求:

# code/http/app/ts/components/YouTubeSearchComponent.ts

 return this.http.get(queryUrl)
      .map((response: Response) => {
        return (<any>response.json()).items.map(item => {
          // console.log("raw item", item); // uncomment if you want to debug
          return new SearchResult({
            id: item.id.videoId,
            title: item.snippet.title,
            description: item.snippet.description,
            thumbnailUrl: item.snippet.thumbnails.high.url
      }); 
  });
});

在这里我们返回http.get的值,我们使用map从请求中获得响应,我们使用json()将响应主体转换为对象,通过map遍历它们为每个item,并将其转换为SearchResult

如果你想看看原item的样子,就取消该console.log的注释,在你的浏览器开发者控制台进行检查。

注意我们调用(<any>response.json()).items.,在这里发生了什么?。在这里我要告诉TypeScript在这里我没有兴趣做严格的类型检查。

当使用JSON API的工作中,我们通常不会给API响应定义类型,所以TypeScript不会知道返回的对象有一个items键,所以编辑器会报错,我们可以称response.json()["items"]强制转换为一个数组。 but in this case (and in creating the SearchResult, it’s just cleaner to use an any type, at the expense of strict type checking

YouTubeService 完整代码

下面是我们YouTubeService的完整代码:

# code/http/app/ts/components/YouTubeSearchComponent.ts

/**
 * YouTubeService connects to the YouTube API
 * See: * https://developers.google.com/youtube/v3/docs/search/list
 */
@Injectable()
export class YouTubeService {
  constructor(public http: Http,
              @Inject(YOUTUBE_API_KEY) private apiKey: string,
              @Inject(YOUTUBE_API_URL) private apiUrl: string) {
  }

  search(query: string): Observable<SearchResult[]> {
    let params: string = [
      `q=${query}`,
      `key=${this.apiKey}`,
      `part=snippet`,
      `type=video`,
      `maxResults=10`
    ].join('&');
    let queryUrl: string = `${this.apiUrl}?${params}`;
    return this.http.get(queryUrl)
      .map((response: Response) => {
        return (<any>response.json()).items.map(item => {
          // console.log("raw item", item); // uncomment if you want to debug
          return new SearchResult({
            id: item.id.videoId,
            title: item.snippet.title,
            description: item.snippet.description,
            thumbnailUrl: item.snippet.thumbnails.high.url
          });
        });
      });
  }
}

编写SearchBox

SearchBox在应用中起着关键的作用:它是我们的UI和youtubeservice之间的协调者。

SearchBox将:

定义SearchBox @Component

来定义我们的 SearchBox @Component:

# code/http/app/ts/components/YouTubeSearchComponent.ts

@Component({
  outputs: ["loading", "results"],
  selector: "search-box"
})

selector,我们已经看到很多次:这允许我们能够创建一个标记。

outputs键指定了从这个组件将要被发射的事件列表。也就是说,我们可以在我们视图中使用(event)="callback()"语法来监听这个组件的事件,例如,这里我们将如何在我们的视图中使用搜索框标记:

<search-box
 (results)="updateResults($event)"
 (loading)="loading = $event"
  ></search-box>

在这个例子中,当SearchBox组件发射一个loading事件,我们将在其父组件上设置loading变量,同样的,当SearchBox发射一个results事件,我们将会将值传递给父组件上的updateResults函数

在@Component配置中,我们只是简单地指定了字符串“loading”和“results”的事件的名称。在这个事件中,每一个事件在控制器类中都会有一个对应的EventEmitter实体变量,等会我们会实现它们。

现在,请记住,@Component就像我们组件的公共API,所以在这里我们只是指定事件的名称,并且之后我们会考虑EventEmitters的实现。

定义SearchBox 模板

我们的视图很简单。就一个input标签:

# code/http/app/ts/components/YouTubeSearchComponent.ts

@Component({
  outputs: ['loading', 'results'],
  selector: 'search-box',
  template: `
    <input type="text" class="form-control" placeholder="Search" autofocus>
  `
})

定义SearchBox控制器

我们的SearchBox控制器是一个新的类:

# code/http/app/ts/components/YouTubeSearchComponent.ts

class SearchBox implements OnInit {
  loading: EventEmitter<boolean> = new EventEmitter<boolean>();
  results: EventEmitter<SearchResult[]> = new EventEmitter<SearchResult[]>();

我们说这个类实现了Oninit,因为我们要使用ngOnInit生命周期事件回调,如果一个类实现了OnInit,那么这个oninit方法将会在第一次变化检测检查后调用。

onInit是一个做初始化(和构造函数比较)的好地方,因为在组件上设置一个属性在构造函数上是不能被接受的

定义SearchBox控制器 constructor

但首先我们讨论构造函数:

# code/http/app/ts/components/YouTubeSearchComponent.ts

constructor(public youtube: YouTubeService,
            private el: ElementRef) {
}

在我们的构造函数上我们注入:

我们同时设置注射为实例变量

定义SearchBox控制器 ngOnInit

在这个Input框上,我想要监听keyup事件,事情是这样的,如果我们只是简单的对每个keyup进行搜索,就不是⼗分理想。有三件事我们可以做,以提高用户体验:

我们可以手动绑定keyup事件,为每一个keyup事件调用函数,并过滤和debouncing,然而,有一个更好的办法,将keyup事件转换为一个Observable流

RxJS提供一种方式来监听元素的事件,使用Rx.Observable.fromEvent,我们可以这样使用它:

# code/http/app/ts/components/YouTubeSearchComponent.ts

ngOnInit(): void {
    // convert the `keyup` event into an observable stream
    Observable.fromEvent(this.el.nativeElement, 'keyup')

注意在fromEvent

现在,我们在这个流上可以进行一些RxJS魔术,把它变成SearchResult。让我们一步一步来。

鉴于 keyup事件流我们可以链接更多的方法,在接下来的几段中,我们将给我们的数据流使用多种函数,这将改变数据流。然后,我们会一起展示整个例子。

首先,让我们提取输入标签的值:

.map((e: any) => e.target.value) // extract the value of the input

以上表达的是,map每一个keyup事件,找出事件目标(e.target表示我们的input元素),并提取这个元素的值,这意味着我们的流现在时一个字符串流

.filter((text: string) => text.length > 1)

filter意味着流将不会发射那些搜索字符串长度小于1的,如果你想忽略短搜索,你可以把这个设置为一个更高的数字。

.debounce(250)

debounce意味不发射那些快于250ms的请求,那就是,我们不会搜索每一次按键,而是在用户暂停输入250MS后进行搜索

.do(() => this.loading.next(true))

使用在流中的方式是为每个事件执行一个函数,但它并没有改变流中的任何东西。这里的想法是,我们要去搜索,它有足够的字符,用户也暂停输入了,所以我们要显示loading指示

this.loading是一个EventEmitter,我们要显示loading,通过发射一个true,我们通过调用EventEmitter的next来发射一些东西,this.loading.next(true)意味着,在loading EventEmitter发射一个true,当我们在这个组件上监听loading事件,$event值现在是true(我们在下面会更密切地关注使用$event)

.flatMapLatest((query: string) => this.youtube.search(query))

从本质上讲,通过flatMapLatest,表示我们要忽略所有的搜索时间,但只保留最新的那次。也就是说,如果一个新的搜索进来,我们要使用最新的和放弃的其余部分。

对于每一个进来的查询,我们将执行我们的YouTubeService搜索。把它们链在一起,我们有这样的:

# code/http/app/ts/components/YouTubeSearchComponent.ts

Observable.fromEvent(this.el.nativeElement, 'keyup')
      .map((e: any) => e.target.value) // extract the value of the input
      .filter((text: string) => text.length > 1) // filter out if empty
      .debounceTime(250)                         // only once every 250ms
      .do(() => this.loading.next(true))         // enable loading
      // search, discarding old events if new input comes in
      .map((query: string) => this.youtube.search(query))
      .switch()

RxJS的API可以是一个有点吓人,因为API功能强大。这就是说,我们的代码非常少行实现了复杂的事件处理流!

因为我们调用YouTubeService我们的流现在已经是SearchResult[]流了。我们可以订阅这个流,并执行相应的动作。

subscribe需要3个参数onSuccess, onError, onCompletion.

# code/http/app/ts/components/YouTubeSearchComponent.ts

.subscribe(
        (results: SearchResult[]) => { // on sucesss
          this.loading.next(false);
          this.results.next(results);
        },
        (err: any) => { // on error
          console.log(err);
          this.loading.next(false);
        },
        () => { // on completion
          this.loading.next(false);
        }
      );

第一个参数指定了,当流发射一个正常的事件我们需要做什么,在这我们发射2个EventEmitters事件

第二个参数指定了当我们的流发生错误时,应该做什么,在这里我们设置this.loading.next(false)并且打印error日志

第3个参数指定了当流完成时应该做什么,在这里我们也是发射loading完成

SearchBox组件 全部代码

连在一起,这是我们的搜索框组件的完整代码:

# code/http/app/ts/components/YouTubeSearchComponent.ts

@Component({
  outputs: ['loading', 'results'],
  selector: 'search-box',
  template: `
    <input type="text" class="form-control" placeholder="Search" autofocus>
  `
})
class SearchBox implements OnInit {
  loading: EventEmitter<boolean> = new EventEmitter<boolean>();
  results: EventEmitter<SearchResult[]> = new EventEmitter<SearchResult[]>();

  constructor(public youtube: YouTubeService,
              private el: ElementRef) {
  }

  ngOnInit(): void {
    // convert the `keyup` event into an observable stream
    Observable.fromEvent(this.el.nativeElement, 'keyup')
      .map((e: any) => e.target.value) // extract the value of the input
      .filter((text: string) => text.length > 1) // filter out if empty
      .debounceTime(250)                         // only once every 250ms
      .do(() => this.loading.next(true))         // enable loading
      // search, discarding old events if new input comes in
      .map((query: string) => this.youtube.search(query))
      .switch()
      // act on the return of the search
      .subscribe(
        (results: SearchResult[]) => { // on sucesss
          this.loading.next(false);
          this.results.next(results);
        },
        (err: any) => { // on error
          console.log(err);
          this.loading.next(false);
        },
        () => { // on completion
          this.loading.next(false);
        }
      );

  }
}

编写SearchResultComponent

SearchBox是相当复杂的。现在让我们来处理一个更容易的组件:SearchResultComponent。该SearchResultComponent的工作就是使一个单一的SearchResult。

这里真的没有什么新的想法,所以让我们把它全部一次写出来:

# code/http/app/ts/components/YouTubeSearchComponent.ts

@Component({
  inputs: ['result'],
  selector: 'search-result',
  template: `
<div class="col-sm-6 col-md-3"> <div class="thumbnail">
<img src="{{result.thumbnailUrl}}"> <div class="caption">
<h3>{{result.title}}</h3> <p>{{result.description}}</p> <p><a href="{{result.videoUrl}}"
class="btn btn-default" role="button">Watch</a></p> </div>
      </div>
    </div>
` })
export class SearchResultComponent {
  result: SearchResult;
}

几件事情

@Component需要有一个属性result,我们将要把SearchResult分配给该组件

模板 显示一个标题,描述和视频的缩略图,然后通过一个按钮链接到视频。

该SearchResultComponent简单地存储SearchResult中实例变量的result。

编写YouTubeSearchComponent

最后一个组件我们要实现YouTubeSearchComponent.这是将一切组成部分联系在一起。

YouTubeSearchComponent @Component
# code/http/app/ts/components/YouTubeSearchComponent.ts

@Component({
  selector: 'youtube-search',
  directives: [SearchBox, SearchResultComponent],

我们的@Component注解是非常简单的,设置selector为youtube-search

YouTubeSearchComponent控制器

我们在看模板之前,先来看下YouTubeSearchComponent控制器

# code/http/app/ts/components/YouTubeSearchComponent.ts

export class YouTubeSearchComponent { 
  results: SearchResult[];
  updateResults(results: SearchResult[]): void {
  this.results = results;
  // console.log("results:", this.results); // uncomment to take a look
  }
}

这个组件持有一个实体变量:results是一个SearchResults[]

我们也定义一个函数updateResults,updateeResults只是需要任何新的SearchResult[]给他,并设置this.results为新值。

我们会在模板中使用Results和updateResults

YouTubeSearchComponent 模板

我们视图需要做3件事

接下去来看看我们的模板,我们在header中建立了一些基本的结构和显示loading gif

# code/http/app/ts/components/YouTubeSearchComponent.ts

template: `
  <div class='container'>
      <div class="page-header">
        <h1>YouTube Search
          <img
            style="float: right;"
            *ng-if="loading"
            src=${loadingGif} />
        </h1>
      </div>

注意 我们的img的src为${loadingGif} 这loadingGif变量来自先前在程序中的声明,在这里,我们采取的是WebPack的图像加载功能 如果您想了解更多有关如何工作,可查看本章示例代码中的webpack配置或者checkout image-wepack-loader

如果loading是true,我们只想显示这个loading图片,所以我们使用ng-if来实现这个功能

接下去让我们看看search-box标签

# code/http/app/ts/components/YouTubeSearchComponent.ts

<div class="row">
<div class="input-group input-group-lg col-md-12">
<search-box
       (loading)="loading = $event"
       (results)="updateResults($event)"
        ></search-box>
  </div>
</div>

这里有趣的是,我们如何绑定到loading和results事件。请注意,我们在这里使用了(event)="action()"

对于loading事件,我们运行表达式 loading = $event. $event的值将会被从EventEmitter发射过来的event值给取代,也就是说,在我们searchBox组件,当我们调用this.loading.next(true),那么这个$event会变 true

同样,都与results事件,当一个新设置的results被发射我们调用updateResults()函数,这有更新我们的组件results实例变量的效果。

最后,我们要在这个组件中循环results渲染每一个result为search-result

# code/http/app/ts/components/YouTubeSearchComponent.ts
<div class="row">
        <search-result
          *ng-for="#result of results"
          [result]="result">
        </search-result>
      </div>
  </div>
    `

YouTubeSearchComponent 完整代码

下面是该YouTubeSearchComponent的完整代码:

# code/http/app/ts/components/YouTubeSearchComponent.ts

@Component({
  selector: 'youtube-search',
  directives: [SearchBox, SearchResultComponent],
  template: `
  <div class='container'>
      <div class="page-header">
        <h1>YouTube Search
          <img
            style="float: right;"
            *ngIf="loading"
            src='${loadingGif}' />
        </h1>
      </div>

      <div class="row">
        <div class="input-group input-group-lg col-md-12">
          <search-box
             (loading)="loading = $event"
             (results)="updateResults($event)"
              ></search-box>
        </div>
      </div>

      <div class="row">
        <search-result
          *ngFor="#result of results"
          [result]="result">
        </search-result>
      </div>
  </div>
  `
})
export class YouTubeSearchComponent {
  results: SearchResult[];

  updateResults(results: SearchResult[]): void {
    this.results = results;
    // console.log("results:", this.results); // uncomment to take a look
  }
}

就讲到这里,一个search-as-you-type YouTube 视频的功能实现,如果你还没有代码,尝试运行示例代码

kittencup commented 8 years ago

angular2 http API

当然,我们创建的所有HTTP请求都是简单的GET请求。重要的是我们要知道们如何创建其他的请求。

创建一个POST请求

使用angular/http创建POST请求非常像GET请求 所不同的是,我们有一个附加参数:body。

[jsonplaceholder API]()还提供了一个URL来测试我们的POST请求,让我们来创建一个POST

# code/http/app/ts/components/MoreHTTPRequests.ts

makePost(): void { 
 this.loading = true;
  this.http.post(
 "http://jsonplaceholder.typicode.com/posts",
 JSON.stringify({
   body: "bar",
   title: "foo",
   userId: 1
 }))
 .toRx()
 .subscribe((res: Response) => {
   this.data = res.json();
   this.loading = false;
 });
}

注意在第2个参数,我们使用JSON.stringify将一个object转换为JSON字符串

PUT / PATCH / DELETE / HEAD

还有其他一些很常见的HTTP请求,我们以大致相同的方式调用他们。

以下是我们可以创建一个DELETE请求:

# code/http/app/ts/components/MoreHTTPRequests.ts

makeDelete(): void {
    this.loading = true;
    this.http.delete("http://jsonplaceholder.typicode.com/posts/1")
      .subscribe((res: Response) => {
        this.data = res.json();
        this.loading = false;
      });
  }

RequestOptions

所有的http方法最后一个参数都是可选的:RequestOptions,该RequestOptions对象封装:

比方说,我们要精心创建一个使用特殊的X-API-TOKEN头的GET请求。我们可以创建一个像这样的header的请求:

# code/http/app/ts/components/MoreHTTPRequests.ts

 makeHeaders(): void {
let headers: Headers = new Headers(); 
headers.append('X-API-TOKEN', 'ng-book');
let opts: RequestOptions = new RequestOptions();
opts.headers = headers;
this.http.get('http://jsonplaceholder.typicode.com/posts/1', opts)
  .subscribe((res: Response) => {
    this.data = res.json();
  });
}

总结

Angular/http 还很年轻,但已经有足够的各种各样的功能API。 一个关于Angular/HTTP的伟大的事情是,它的支持mock backend,测试是非常有用。可以到测试那章,了解更多关于测试的内容。