如何在 Angular 2 beta 的服务中有效地使用 Http 组件?

Posted

技术标签:

【中文标题】如何在 Angular 2 beta 的服务中有效地使用 Http 组件?【英文标题】:How to Consume Http Component efficiently in a service in angular 2 beta? 【发布时间】:2016-03-30 17:47:56 【问题描述】:

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

我读了this 和 我知道在 Angular 2(与 Angular 1 不同)中,Http 组件不是返回 Promise 的服务。它返回一个叫做 Observable 的东西。我们知道一个组件最好不要直接使用Http。有效的方法是制作一个负责消费Http的服务。但是怎么办?!这是否应该在完成请求后返回一个承诺? (看here)

这有意义吗?!

【问题讨论】:

可以通过添加.toPromise() 后跟.then() 调用链来使用HTTP 作为承诺。尽管如此,observables 是推荐的方法。 @EvanPlaice 是的,我读过它们,现在我是 Observables 的粉丝 :) 看看这个***.com/a/34758630/5043867 【参考方案1】:

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

如果我们从服务器获取 array原始类型(即字符串、数字、布尔值),我们可以通过使用返回的可以直接在我们的模板中使用asyncPipe 观察。该管道将​​自动订阅可观察对象(它也适用于承诺),并将返回可观察对象发出的最新值。当发出新值时,管道会标记要检查更改的组件,因此视图将自动更新为新值。

如果我们从服务器获取 对象我不知道有什么方法可以使用 asyncPipe,我们可以使用 async 管道,结合安全导航算子如下:

(objectData$ | async)?.name

但这看起来很复杂,我们必须为我们想要显示的每个对象属性重复此操作。

相反,我建议我们 subscribe() 到组件中的 observable 并将包含的对象存储到组件属性中。然后我们在模板中使用safe navigation operator (?.) 或(正如@Evan Plaice 在评论中提到的)NgIf。如果我们不使用安全导航操作符或 NgIf,当模板第一次尝试渲染时会抛出错误,因为对象还没有填充值。

注意下面的服务如何总是为每个 get 方法返回一个 observable。

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”。

数据/array.json

[ 1,2,3 ]

数据/primitive.json

Greetings SO friends!

数据/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


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

【讨论】:

? (elvis) 运算符的一个有用替代方法是将*ngIf 条件添加到将使用数据的模板部分。它提供了更粗粒度的控制,因此您不必担心将 elvis 运算符洒在整个模板上,也不必担心模板在没有数据的情况下呈现时的外观。 @EvanPlaice,谢谢,我更新了答案以包含您的建议。【参考方案2】:

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 管道也可以用于隐式订阅可观察对象。这是使用它的方法。首先更新你的组件,将 observable 对象放入你要显示的属性中:

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

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

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

然后在模板中使用异步管道:

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

这篇文章分为两部分也可以提供更多细节:

http://restlet.com/blog/2015/12/30/implementing-an-angular-2-frontend-over-an-apispark-hosted-web-api-part-1/ http://restlet.com/blog/2016/01/06/implementing-an-angular-2-frontend-over-an-apispark-hosted-web-api-part-2/

希望对你有帮助 蒂埃里

【讨论】:

您可能希望使用异步管道而不是手动订阅。 非常感谢@foox 的评论!我更新了我的答案以描述如何使用异步管道;-) 一个小问题,您在引导程序中导入了HTTP_PROVIDERS,但注入了ROUTER_PROVIDERS。是错字吗? 我可以将其作为一个单独的问题,但这只是您答案的一个小插件。如何在 http 请求中使用间隔? 很好,我一直在寻找一个简单的例子来演示如何使用async 管道。

以上是关于如何在 Angular 2 beta 的服务中有效地使用 Http 组件?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 angular 2 (v2.0.0-beta.15) 实现拖放

Angular2 beta 7 上的 HashLocationStrategy

angular-cli beta 14 - 如何导入环境?

当我使用最新的 angular-cli(beta 15 w/webpack)运行 ng test 时,如何提供资产?

Angular 2 beta – 选择不起作用(Chrome 除外)

如何正确将 angular 2 (npm) 升级到最新版本?