第八章 HTTP

注:学习使用,禁止转载

介绍

angular有它自己的Http库,我们可以使用他们去调用外部的数据和API。

当我们去请求外部数据的时候,我们希望页面能够与用户继续保持交互。也就是说,我们不希望我们的页面一直冻结到外部数据返回,为了完成这个,我们的HTTP请求时异步的。

历史证明,处理异步代码比处理同步代码复杂得多。
在javascript中,有三种方式去处理异步代码:

  1. Callbacks
  2. Promises
  3. Observables

在angular2中,首选的方式就是使用Observables,因此,这一章我们会讲解这部分的内容。

在这章中,我们学习:

  1. 展示一个HTTP的基本例子
  2. 创建一个YouTuBe的search-as-you-type组件
  3. 讨论Http库里面的API细节

:fa-info-circle:这一张的完整代码在http文件夹下,这里面包含一个readme.md文件,它讲解了怎么去运行这个例子的方法。

使用@angular/http

在angular2中,http单独放在了一个模块里面。这也就意味着,为了使用它,你必须import @angular/http。比如,我们可能会像下面这样:

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

import from @angular/http

在我们的例子中,我们使用import HTTP_PROVIDERS,它是一个快捷的方式去引入http里面的指令。

code/http/app/ts/app.ts

import {
  Component
} from '@angular/core';
import { bootstrap } from '@angular/platform-browser-dynamic';
import { HTTP_PROVIDERS } from '@angular/http';

当我们运行app时,我们会添加HTTP_PROVIDERS作为一个依赖。结果就是我们可以在我们的app里面注入http模块里面的内容。

bootstrap(HttpApp, [ HTTP_PROVIDERS ]);

现在,我们可以注入http服务进我们的组件里面了。

class MyFooComponent {
 constructor(public http: Http) {
}

 makeRequest(): void {
 // do something with this.http ...
 }
 }

一个基本的请求

我们将要做的第一件事情就是做一个简单的GET请求。
我们要做的是:
1. 有一个button去调用马克Request
2. makeRequest去调用http库完成一个GET请求
3. 当请求返回的时候,使用返回的数据去更新this.data。并在视图上面渲染出来。

下面是我们的截图:

这里写图片描述

构建一个简单的SimpleHTTPComponent组件

我们要做的第一件事情就是去import一些http模块,并且表明组件的selector。
code/http/app/ts/components/SimpleHTTPComponent.ts

import {Component} from '@angular/core';
import {Http, Response} from '@angular/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>
`
})

这个模板有三个有趣的部分:
1. button
2. loading indicator
3. data,数据

button上面,(click)绑定了makeRequest
为了提醒用户,我们正在加载数据,使用ngIf去查看loading是不是true,如果是显示loading….
data是一个对象,为了调试,使用json管道,它会返回一个易读好看的格式

构建SimpleHTTPComponent控制器

我们为了我们的组件定义一个新类:SimpleHTTPComponent:
code/http/app/ts/components/SimpleHTTPComponent.ts

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

我们有两个变量,data接收数据,loading代表是否在加载。

接下来定义构造器:

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

 constructor(public http: Http) {
  }

这里注入了一个关键的模块:http

下面是makeRequest函数的实现:
code/http/app/ts/components/SimpleHTTPComponent.ts

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

当我们调用马克Request 的时候的第一件事情就是设置loading为true, 让loading指示器显示出来。HTTPreqeust的请求时很直接的。调用http.reqeust,然后传递想要请求的URL就可以了。

http.request返回一个Observable,我们可以subscribe去订阅它的改变(类似于then与Promise的关系)。
code/http/app/ts/components/SimpleHTTPComponent.ts

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

当http.request返回的时候会发射一个Response对象,我们分解对象的body作为json对象,然后赋值给daa。同时,请求完成后,设置loading为false,表示我们加载完成。

SimpleHTTPComponent全部代码

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

/*
 * Angular
 */
import {Component} from '@angular/core';
import {Http, Response} from '@angular/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;
      });
  }
}

编写YouTubeSearchComponent

上面的例子比较简单,现在我们建立一个更加复杂的例子。

在这里,我们希望构建一个按你自己的type去youtube搜索的方式。当搜索返回的时候,我们会显示每一个视频的缩略图及描述和视频的地址。

下面是当我们搜索“cats playing ipads”的画面:

这里写图片描述

这个例子中,我们将会编写几样东西:
1. 存储数据的SearchResult对象
2. 一个YouTubeService,它管理请求的API以及将请求的结果转换为一个SearchResult[]的流
3. 一个SearchBox组件
4. 一个SearchResultComponent组件,显示一个SearchResult
5. 一个YouTubeSearchComponent会封装我们所有的代码并渲染SearchResult列表。

编写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}`;
  }
}

这里仅仅需要指出的时候,当videoUrl为空时,我们硬编码了一个url:https://www.youtube.com/watch?v=${this.id}

编写YouTubeService

API

这个例子中,我们使用the YouTube v3 search API。

我们会指定两个常量,去映射我们的YouTubeService到我们的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。

为了有利于这个事情,通常我们会使用注入参数做这个事情。

为什么要使用注入而不是通常的方式呢?因为如果我们使用注入的方式,我们可以:
1. 在部署的时候,根据不同的环境设置不同的值
2. 测试的时候,很容易更换注入的值

通过注入这些值,使得我们的app更加灵活。

为了注入这些值,我们使用bind(…).toValue(…)的语法,像下面这样:

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

export var youTubeServiceInjectables: Array<any> = [
  bind(YouTubeService).toClass(YouTubeService),
  bind(YOUTUBE_API_KEY).toValue(YOUTUBE_API_KEY),
  bind(YOUTUBE_API_URL).toValue(YOUTUBE_API_URL)
];

在这里,我们指定要绑定YOUTUBE_API_KEY注入到YOUTUBE_API_KEY的值。
如果你还记得,为了让注入在我们的app中可用,需要在依赖启动的时候设置。因为我们在这里导出了youTubeServiceInjectables,所以,我们可以在我们的app中导入它。

// http/app.ts
 import { youTubeServiceInjectables } from "components/YouTubeSearchComponent";

 // ....
 // further down
 bootstrap(HttpApp, [ HTTP_PROVIDERS, youTubeServiceInjectables ]);

现在我们可以注入YOUTUBE_API_KEY,而不直接使用变量。

YouTubeService构造器

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) {
  }

在构造器中,我们注入了三样东西:
1. Http
2. YOUTUBE_API_KEY
3. YOUTUBE_API_URL

请注意,我们使用三个实例变量来接收注入,所以我们在其他地方可以使用http,apiKey,apiUrl来访问这些注入的内容。

注意,我们使用@Inject显式注入。

YouTubeService的搜索

现在,让我们实现search函数,search函数带一个查询字符串的参数并返回一个可以发射一个SearchResult[]对象的流。意思就是说,每重点内容一次发射一个SearchResult的数组:

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

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}`;

这里,我们手动建立请求的url,然后我们连接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的返回值使用map映射得到相应的对象,然后从这个对象中解构出body部分,使用。json转换成对象,然后迭代转换成SearchResult。

YouTubeService完整代码

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

export var YOUTUBE_API_KEY:string = 'AIzaSyDOfT_BO81aEZScosfTYMruJobmpjqNeEk';
export var YOUTUBE_API_URL:string = 'https://www.googleapis.com/youtube/v3/search';
let loadingGif:string = ((<any>window).__karma__) ? '' : require('images/loading.gif');

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}`;
    }
}

/**
 * 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
                    });
                });
            });
    }
}

export var youTubeServiceInjectables:Array<any> = [
    bind(YouTubeService).toClass(YouTubeService),
    bind(YOUTUBE_API_KEY).toValue(YOUTUBE_API_KEY),
    bind(YOUTUBE_API_URL).toValue(YOUTUBE_API_URL)
];

编写SearchBox

SearchBox组件在我们的app中扮演一个关键的角色:它在我们的UI与YouTubeService中扮演一个中介的角色。

SearchBox会:
1. 关注input的输入,并提交一个搜索给YouTubeService。
2. 当我们loading的时候发送一个loading事件
3. 当我们有新的结果的时候发送一个results事件

SearchBox @Component定义

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

/**
 * SearchBox displays the search box and emits events based on the results
 */

@Component({
    outputs: ['loading', 'results'],
    selector: 'search-box',

outputs指明了这个组件会发送的事件。在外面我们可以监听。

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

在这个例子中,当发送一个loading事件时,外面进行判断处理,当发送一个results事件时,父组件调用它的updateResults函数。

对应的,每一个output对应了一个EventEmitter对象。

SearchBox 模板

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

template: `
    <input type="text" class="form-control" placeholder="Search" autofocus>
  `
SearchBox的控制器定义

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

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

这两个EventEmitter对象对应上面的两个输出。
并且实现了OnInit接口,也就是说,会回调ngOnInit函数,ngOnInit函数中处理初始化时相当好的方式。

构造器:
code/http/app/ts/components/YouTubeSearchComponent.ts

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

构造器注入了一个YouTubeService,一个el,它代表本身这个组件的DOM元素。

ngOnInit:
在这个例子中,我们希望去监听按键释放事件,意思是说,如果我们简单的让每个按键释放的时候都去搜索不会太好,这里我们可以做三件事情去改善用户体验:
1. 过滤空的和简短的搜索
2. 对input防抖动,意思就是说,不要输入每一个字符都去搜索一边,而是当用户输入完成一段时间之后再去搜索,当用户连续输入的时候不去搜索。
3. 当用户进行一个新的搜索后,丢弃旧的搜索

我们可以手动绑定一个函数给keyup,然后实现过滤和防抖动。然而这里有一个更好的方式:将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')
            .map((e:any) => e.target.value) // extract the value of the input

注意fromEvent的两个参数:
1. this.el.nativeElement:代表想要关注的DOM元素
2. keyup:要转换成stream的事件名称

现在,我们可以执行一些RxJS的魔法,将这个流转换进SearchResult。让我们一步一步讲解:
获取keyup事件流让我们可以连接更多的方法,在下面,我们会连接几个函数进我们的流,它们将会转换流,最后,我们会展示整个代码。

首先,我们解析input的值:

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

上面的意思就是,映射我们的keyup事件,找到他的target(在这个例子中是input),然后获取它的值,这就意味着我们的流现在变成了一个strings的流。

接下来:

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

这个意思就是说,当字符数少于1的时候,不发送任何事件。

接下来:

.debounceTime(250)

意味着,我们会至少等待250毫秒后才请求,也就不会导致一直请求。

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

使用do,就是在流的中间执行一个函数,但是它不改变流的任何东西。意思就是说,我们的字符数够了,时间也够了我们将要执行一次搜索,在搜索之前,我们将loading设置为true。

.map((query: string) => this.youtube.search(query))
.switch()

我们使用map去实行一次搜索,针对每个发送的事件,通过使用swith,意味着我们会忽略所有的搜索事件,只保留最新的一个。

全部组合起来就是:
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可能是有点令人生畏的。这个就是意味着,我们可以使用很少的代码完成很多的事情。

因为我们调用我们的YouTubeService是一个SearchResult[]的流,我们可以订阅这个流去执行一些完成操作。
subscribe需要三个参数,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);
                }
            );

onSuccess:是当流执行成功时调用
onError:当流发生错误的时候调用
onCompletion:当流完成的时候调用。

SearchBox完整代码

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

/**
 * SearchBox displays the search box and emits events based on the results
 */

@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,它仅仅渲染一个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;
}

编写YouTubeSearchComponent

我们需要实现的最后一个组件是YouTubeSearchComponent。它将所有的东西组装起来。

YouTubeSearchComponent @Component

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

@Component({
    selector: 'youtube-search',
    directives: [SearchBox, SearchResultComponent],
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
    }
}

太简单,不解释。

YouTubeSearchComponent 模板

我们的视图需要做三件事情:
1. 显示加载指示器
2. 监听search-box的事件
3. 显示搜索结果

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

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

使用一个图片来显示正在加载。

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>

包装search-box,监听loading事件和results事件。

最后,显示结果:

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

<div class="row">
        <search-result
          *ngFor="let result of results"
          [result]="result">
        </search-result>
      </div>
  </div>

使用ngFor显示列表。

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="let 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
    }
}

@angular/http API

当然,到目前为止,我们都是使用的简单的HTTP GET请求。我们需要知道怎么去做其他的请求也是很重要的。

POST请求

@angular/http的POST请求与GET请求很像,我们只需要添加一个参数:body。

如下:
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
      }))
      .subscribe((res: Response) => {
        this.data = res.json();
        this.loading = false;
      });
  }

注意,第二个参数,我们传递了一个对象,并使用JSON.stringify转换成了json字符串

PUT / PATCH / DELETE / HEAD请求

这里有一些其他通用的HTTP请求,使用几乎相同的方式调用它们:

  • http.put 和 http.patch分别对应PUT和PATCH请求,它需要一个URL和一个body。
  • http.delete和http.head对应DELETE和HEAD请求,只需要一个URL,不需要body。

下面是表示怎么进行一个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;
      });
  }

请求选项

到目前为止,我们讲的所有http函数都有一个可选的参数:RequestOptions.这个RequestOptions.封装了:

  • method
  • headers
  • body
  • mode
  • credentials
  • cache
  • url
  • search

让我们看看怎么样去创建一个特定X-API-TOKEN头的GET请求,我们可以创建一个像下面这样的请求:

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还比较早,但是它已经全特性支持了。

关于@angular/http一个重要的事情是,它支持mock测试,它对单元测试来说是一个非常好的帮助。关于测试的详细内容见测试一节。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值