如何在Angular 2 beta中高效地使用Http组件进行服务消费?

18

我正尝试着使用 Angular 2-beta 并且想要使用 Http 组件。但是这里有一个严重的问题:

我读了这篇文章,并且知道在 Angular 2 中(不像 Angular 1),Http 组件不是返回 Promise 的服务。它会返回一些叫做 Observable 的东西。我们知道组件最好不要直接使用 Http。效率更高的方法是创建一个负责消耗 Http 的服务。但是怎么做?这个服务在完成请求后应该返回一个 Promise 吗?(请参阅此处

这到底有意义吗?!


1
你可以在链式调用.then()后添加.toPromise(),将HTTP转换为Promise。但是,使用可观察对象是推荐的方法。 - Evan Plaice
1
@EvanPlaice 是的,我了解它们,现在我是 Observables 的粉丝 :) - soroush gholamzadeh
请查看此链接:https://dev59.com/q1sW5IYBdhLWcg3woYcj#34758630 - Pardeep Jain
2个回答

25

Angular 2可以实现服务,它们只是可注入类的对应项,如下所述。在这种情况下,此类可以注入到其他元素中,例如组件。

import {Injectable} from 'angular2/core';
import {Http, Headers} from 'angular2/http';
import 'rxjs/add/operator/map';

@Injectable()
export class CompanyService {
  constructor(http:Http) {
    this.http = http;
  }
}

在启动应用程序的主组件时,如果指定了HTTP_PROVIDERS,则可以在其中注入一个Http对象(使用其构造函数):

import {bootstrap} from 'angular2/platform/browser'
import {HTTP_PROVIDERS} from 'angular2/http';
import {AppComponent} from './app.component'

bootstrap(AppComponent, [
  HTTP_PROVIDERS
]);

接下来可以将此服务注入到组件中,具体步骤如下。不要忘记在组件的providers列表中指定它。

import { Component, View, Inject } from 'angular2/core';
import { CompanyService } from './company-service';

@Component({
  selector: 'company-list',
  providers: [ CompanyService ],
  template: `
    (...)  `
})

export class CompanyList {
  constructor(private service: CompanyService) {
    this.service = service;
  }
}

接下来您可以在服务中实现一个利用Http对象的方法,并返回对应于您请求的Observable对象:

@Injectable()
export class CompanyService {
  constructor(http:Http) {
    this.http = http;
  }

  getCompanies() {
    return this.http.get('https://angular2.apispark.net/v1/companies/')
                  .map(res => res.json());
  }
}

组件可以调用这个getCompanies方法并订阅Observable对象上的回调,在响应到达时通知更新组件的状态(就像在Angular1中使用Promise一样):
export class CompanyList implements OnInit {
  public companies: Company[];

  constructor(private service: CompanyService) {
    this.service = service;
  }

  ngOnInit() {
    this.service.getCompanies().subscribe(
      data => this.companies = data);
  }
}

编辑

正如foxx在他的评论中建议的那样,async管道也可以用于隐式订阅可观察对象。以下是使用它的方法。首先更新您的组件,将可观察对象放入要显示的属性中:

export class CompanyList implements OnInit {
  public companies: Company[];

  constructor(private service: CompanyService) {
    this.service = service;
  }

  ngOnInit() {
    this.companies = this.service.getCompanies();
  }
}

在您的模板中使用 async 管道:

@Component({
  selector: 'company-list',
  providers: [ CompanyService ],
  template: `
    <ul>
      <li *ngFor="#company of companies | async">{{company.name}}</li>
    </ul>
  `
})
export class CompanyList implements OnInit {
  (...)
}

这篇文章分两部分,可以提供更多细节: 希望这能对你有所帮助, Thierry

7
你可能想使用异步管道(async pipe)而不是手动订阅。 - fxck
非常感谢@foox的评论!我更新了我的答案,描述了如何使用异步管道;-) - Thierry Templier
一个小问题,您在引导程序中导入了HTTP_PROVIDERS,但注入的是ROUTER_PROVIDERS。 这是一个笔误吗? - soroush gholamzadeh
我可以把它作为一个单独的问题,但这只是你答案的一个小附加。如何在HTTP请求中使用间隔? - M.D.
不错,我一直在寻找一个简单的示例来演示如何使用“async”管道。 - Evan Plaice

7

不需要将Http的get()方法返回的observable转换为promise。在大多数情况下,服务可以直接返回observable。

如果我们从服务器获取一个数组原始类型(即字符串、数字、布尔值),我们可以通过在模板中使用返回的observable并使用asyncPipe来简化控制器逻辑。这个管道会自动订阅observable(它也适用于promise),并返回observable最近发出的值。当发出新值时,该管道会标记组件以检查更改,因此视图将自动更新为新值。

如果我们从服务器获取一个对象我不知道如何使用asyncPipe,我们可以使用async管道,与安全导航运算符结合使用,如下所示:

{{(objectData$ | async)?.name}}

但是看起来很复杂,而且我们必须重复这样做以显示每个对象属性。
相反,我建议我们在组件中subscribe()到可观察对象并将包含的对象存储到组件属性中。然后,在模板中使用安全导航运算符(?.)或(如@Evan Plaice在评论中提到的那样)NgIf。如果不使用安全导航运算符或NgIf,当模板首次尝试呈现对象尚未填充值时,将抛出错误。
请注意,以下服务始终为每个get方法返回一个可观察对象。

service.ts

import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
import 'rxjs/add/operator/map';  // we need to import this now

@Injectable()
export class MyService {
  constructor(private _http:Http) {}
  getArrayDataObservable() {
    return this._http.get('./data/array.json')
      .map(data => data.json());
  }
  getPrimitiveDataObservable() {
    return this._http.get('./data/primitive.txt')
      .map(data => data.text());   // note .text() here
  }
  getObjectDataObservable() {
    return this._http.get('./data/object.json')
      .map(data => data.json());
  }
}

app.ts

import {Component} from 'angular2/core';
import {MyService} from './my-service.service';
import {HTTP_PROVIDERS} from 'angular2/http';

@Component({
  selector: 'my-app',
  providers: [HTTP_PROVIDERS, MyService],
  template: `
    <div>array data using '| async':
      <div *ngFor="#item of arrayData$ | async">{{item}}</div>
    </div>
    <div>primitive data using '| async': {{primitiveData$ | async}}</div>
    <div>object data using ?.: {{objectData?.name}}</div>
    <div *ngIf="objectData">object data using NgIf: {{objectData.name}}</div>`
})
export class AppComponent {
  constructor(private _myService:MyService) { console.clear(); }
  ngOnInit() {
    this.arrayData$     = this._myService.getArrayDataObservable();
    this.primitiveData$ = this._myService.getPrimitiveDataObservable();
    this._myService.getObjectDataObservable()
      .subscribe(data => this.objectData = data);
  }
}

注意:我在服务方法名称中加入了“Observable” - 例如getArrayDataObervable() - 只是为了突出这个方法返回一个Observable。通常你不会在名称中加入“Observable”。

data/array.json

[ 1,2,3 ]

data/primitive.json

Greetings SO friends!

data/object.json

{ "name": "Mark" }

输出:

array data using '| async':
1
2
3
primitive data using '| async': Greetings SO friends!
object data using .?: Mark
object data using NgIf: Mark

Plunker

可以翻译为:

{{链接1:Plunker}}


使用async管道的一个缺点是在组件中没有处理服务器错误的机制。我回答了另一个问题,解释了如何在组件中捕获这样的错误,但在这种情况下我们总是需要使用subscribe()

1
一种有用的替代?(Elvis)运算符的方法是在将使用数据的模板部分中添加一个*ngIf条件。它提供了更粗粒度的控制,因此您不必担心在整个模板中随处添加Elvis运算符,也不必担心模板在没有数据的情况下的呈现方式。 - Evan Plaice
@EvanPlaice,谢谢,我更新了答案以包含您的建议。 - Mark Rajcok

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接