从 Angular 2 服务创建和返回 Observable

Posted

技术标签:

【中文标题】从 Angular 2 服务创建和返回 Observable【英文标题】:Creating and returning Observable from Angular 2 Service 【发布时间】:2016-02-13 23:54:58 【问题描述】:

这更像是一个“最佳实践”问题。共有三个玩家:ComponentServiceModelComponent 正在调用 Service 从数据库中获取数据。 Service 正在使用:

this.people = http.get('api/people.json').map(res => res.json());

返回Observable

Component 可以订阅Observable

    peopleService.people
        .subscribe(people => this.people = people);
      

但是,我真正想要的是 Service 返回一个 Array of Model 对象,该对象是从 Service 从数据库中检索到的数据创建的。我意识到Component 可以在 subscribe 方法中创建这个数组,但我认为如果服务这样做并将其提供给Component 会更干净。

Service 如何创建一个包含该数组的新 Observable 并将其返回?

【问题讨论】:

【参考方案1】:

更新:2016 年 9 月 24 日 Angular 2.0 稳定版

这个问题仍然有很多流量,所以我想更新它。由于 Alpha、Beta 和 7 个 RC 候选者的疯狂变化,我停止更新我的 SO 答案,直到它们稳定为止。

这是使用Subjects 和ReplaySubjects 的完美案例

个人更喜欢使用ReplaySubject(1),因为它允许在新订阅者附加时传递最后存储的值,即使迟到:

let project = new ReplaySubject(1);

//subscribe
project.subscribe(result => console.log('Subscription Streaming:', result));

http.get('path/to/whatever/projects/1234').subscribe(result => 
    //push onto subject
    project.next(result));

    //add delayed subscription AFTER loaded
    setTimeout(()=> project.subscribe(result => console.log('Delayed Stream:', result)), 3000);
);

//Output
//Subscription Streaming: 1234
//*After load and delay*
//Delayed Stream: 1234

因此,即使我延迟连接或需要稍后加载,我也总能得到最新的呼叫,而不必担心错过回调。

这也让您可以使用相同的流向下推送:

project.next(5678);
//output
//Subscription Streaming: 5678

但是,如果您 100% 确定您只需要调用一次呢?保留开放的主题和可观察对象并不好,但总有“如果?”

这就是AsyncSubject 的用武之地。

let project = new AsyncSubject();

//subscribe
project.subscribe(result => console.log('Subscription Streaming:', result),
                  err => console.log(err),
                  () => console.log('Completed'));

http.get('path/to/whatever/projects/1234').subscribe(result => 
    //push onto subject and complete
    project.next(result));
    project.complete();

    //add a subscription even though completed
    setTimeout(() => project.subscribe(project => console.log('Delayed Sub:', project)), 2000);
);

//Output
//Subscription Streaming: 1234
//Completed
//*After delay and completed*
//Delayed Sub: 1234

太棒了!即使我们关闭了主题,它仍然回复了它加载的最后一个内容。

另一件事是我们如何订阅该 http 调用并处理响应。 Map 非常适合处理回复。

public call = http.get(whatever).map(res => res.json())

但是如果我们需要嵌套这些调用怎么办?是的,您可以使用具有特殊功能的主题:

getThing() 
    resultSubject = new ReplaySubject(1);

    http.get('path').subscribe(result1 => 
        http.get('other/path/' + result1).get.subscribe(response2 => 
            http.get('another/' + response2).subscribe(res3 => resultSubject.next(res3))
        )
    )
    return resultSubject;

var myThing = getThing();

但这太多了,这意味着您需要一个函数来完成它。输入FlatMap:

var myThing = http.get('path').flatMap(result1 => 
                    http.get('other/' + result1).flatMap(response2 => 
                        http.get('another/' + response2)));

太好了,var 是一个从最终 http 调用中获取数据的 observable。

好的,但我想要 angular2 服务!

我找到你了:

import  Injectable  from '@angular/core';
import  Http, Response  from '@angular/http';
import  ReplaySubject  from 'rxjs';

@Injectable()
export class ProjectService 

  public activeProject:ReplaySubject<any> = new ReplaySubject(1);

  constructor(private http: Http) 

  //load the project
  public load(projectId) 
    console.log('Loading Project:' + projectId, Date.now());
    this.http.get('/projects/' + projectId).subscribe(res => this.activeProject.next(res));
    return this.activeProject;
  

 

 //component

@Component(
    selector: 'nav',
    template: `<div>project?.name<a (click)="load('1234')">Load 1234</a></div>`
)
 export class navComponent implements OnInit 
    public project:any;

    constructor(private projectService:ProjectService) 

    ngOnInit() 
        this.projectService.activeProject.subscribe(active => this.project = active);
    

    public load(projectId:string) 
        this.projectService.load(projectId);
    

 

我是观察者和可观察者的忠实粉丝,所以我希望这次更新能有所帮助!

原答案

我认为这是使用Observable Subject 或Angular2 EventEmitter 的用例。

在您的服务中,您创建一个EventEmitter,允许您将值推送到它上面。在 Alpha 45 中,您必须将其转换为 toRx(),但我知道他们正在努力摆脱这种情况,因此在 Alpha 46 中您可以简单地返回EvenEmitter

class EventService 
  _emitter: EventEmitter = new EventEmitter();
  rxEmitter: any;
  constructor() 
    this.rxEmitter = this._emitter.toRx();
  
  doSomething(data)
    this.rxEmitter.next(data);
  

这种方式有一个EventEmitter,您的不同服务功能现在可以推送到它上面。

如果你想直接从调用中返回一个 observable,你可以这样做:

myHttpCall(path) 
    return Observable.create(observer => 
        http.get(path).map(res => res.json()).subscribe((result) => 
            //do something with result. 
            var newResultArray = mySpecialArrayFunction(result);
            observer.next(newResultArray);
            //call complete if you want to close this stream (like a promise)
            observer.complete();
        );
    );

这将允许您在组件中执行此操作: peopleService.myHttpCall('path').subscribe(people =&gt; this.people = people);

并弄乱您服务中调用的结果。

我喜欢自己创建 EventEmitter 流,以防我需要从其他组件访问它,但我可以看到两种方式都在工作......

这是一个显示带有事件发射器的基本服务的 plunker:Plunkr

【讨论】:

我尝试了这种方法,但得到“不能将'new'与类型缺少调用或构造签名的表达式一起使用”-错误​​。有人知道该怎么做吗? @Spock 自从这个原始问题以来,规范似乎已经更新。您不再需要可观察对象的“新”,因为它为您执行此操作。只需删除新的并让我知道会发生什么。我现在正在搞砸一些东西,如果它也适合你,我会更新这个答案 不鼓励将EventEmitter 用于除@Output() 之外的任何内容。另见***.com/questions/34376854/… @GünterZöchbauer,是的,现在是......当时它将会是 EventEmitters,但他们已经在 Rx Observables 上标准化。我的 Observable 示例仍然有效,但如果您要使用我给出的 EventEmitter 示例,我建议直接使用主题:github.com/Reactive-Extensions/RxJS/blob/master/doc/api/… @maxisam 感谢您的编辑,尽管答案是/是相对于 Alpha 删除 Observable 的“新”现在是正确的【参考方案2】:

这是来自Angular2 docs 的示例,说明如何创建和使用自己的 Observable:

服务

import Injectable from 'angular2/core'
import Subject    from 'rxjs/Subject';
@Injectable()
export class MissionService 
  private _missionAnnouncedSource = new Subject<string>();
  missionAnnounced$ = this._missionAnnouncedSource.asObservable();

  announceMission(mission: string) 
    this._missionAnnouncedSource.next(mission)
  

组件

    import Component          from 'angular2/core';
    import MissionService     from './mission.service';

    export class MissionControlComponent 
      mission: string;

      constructor(private missionService: MissionService) 

        missionService.missionAnnounced$.subscribe(
          mission => 
            this.mission = mission;
          )
      

      announce() 
        this.missionService.announceMission('some mission name');
      
    

完整的工作示例可以在这里找到: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

【讨论】:

【参考方案3】:

我想补充一点,如果创建的对象是静态的并且不是通过 http 来的,那么可以这样做:

public fetchModel(uuid: string = undefined): Observable<string> 
      if(!uuid)  //static data
        return Observable.of(new TestModel()).map(o => JSON.stringify(o));
      
      else 
        return this.http.get("http://localhost:8080/myapp/api/model/" + uuid)
                .map(res => res.text());
      
    

编辑: 对于 Angular 7.x.x 映射需要使用 pipe() 来完成,如此处所述 (https://***.com/a/54085359/986160):

import of,  Observable  from 'rxjs';
import  map  from 'rxjs/operators';
[...]
public fetchModel(uuid: string = undefined): Observable<string> 
      if(!uuid)  //static data
        return of(new TestModel());
      
      else 
        return this.http.get("http://localhost:8080/myapp/api/model/" + uuid)
                .pipe(map((res:any) => res)) //already contains json
      
    

来自对我关于观察者和静态数据的问题的回答:https://***.com/a/35219772/986160

【讨论】:

【参考方案4】:

我参加聚会有点晚了,但我认为我的方法的优点是它没有使用 EventEmitters 和 Subjects。

所以,这是我的方法。我们无法摆脱 subscribe(),我们也不想这样做。在这种情况下,我们的服务将返回一个Observable&lt;T&gt;,并带有一位拥有我们珍贵货物的观察员。我们将从调用者初始化一个变量Observable&lt;T&gt;,它将获得服务的Observable&lt;T&gt;。接下来,我们将订阅这个对象。最后,你得到你的“T”!来自您的服务。

首先,我们的人员服务,但您的不传递参数,这更现实:

people(hairColor: string): Observable<People> 
   this.url = "api/" + hairColor + "/people.json";

   return Observable.create(observer => 
      http.get(this.url)
          .map(res => res.json())
          .subscribe((data) => 
             this._people = data

             observer.next(this._people);
             observer.complete();


          );
   );

好的,如您所见,我们返回一个“人”类型的Observable。方法的签名,竟然这么说!我们将_people 对象塞入我们的观察者。接下来,我们将从组件中的调用方访问此类型!

在组件中:

private _peopleObservable: Observable<people>;

constructor(private peopleService: PeopleService)

getPeople(hairColor:string) 
   this._peopleObservable = this.peopleService.people(hairColor);

   this._peopleObservable.subscribe((data) => 
      this.people = data;
   );

我们通过从PeopleService 返回Observable&lt;people&gt; 来初始化我们的_peopleObservable。然后,我们订阅这个属性。最后,我们将this.people 设置为我们的数据(people)响应。

以这种方式构建服务与典型服务相比有一个主要优势:map(...) 和组件:“subscribe(...)”模式。在现实世界中,我们需要将 json 映射到我们类中的属性,有时,我们会在那里做一些自定义的事情。所以这个映射可以发生在我们的服务中。而且,通常,因为我们的服务调用不会被使用一次,但可能在我们代码的其他地方,我们不必再次在某些组件中执行该映射。此外,如果我们向人员添加一个新字段怎么办?....

【讨论】:

我同意格式化应该在服务中,并且我也发布了一个标准的 Observable 方法,但是服务中 Subjects 的优点是可以触发其他功能。如果您总是只需要直接的 http 调用,那么我会使用 Observable 方法..【参考方案5】:

在 service.ts 文件中 -

一个。从 observable/of 导入“of” 湾。创建一个 json 列表 C。使用 Observable.of() 返回 json 对象 前任。 -

import  Injectable  from '@angular/core';
import  Observable  from 'rxjs/Observable';
import  of  from 'rxjs/observable/of';

@Injectable()
export class ClientListService 
    private clientList;

    constructor() 
        this.clientList = [
            name: 'abc', address: 'Railpar',
            name: 'def', address: 'Railpar 2',
            name: 'ghi', address: 'Panagarh',
            name: 'jkl', address: 'Panagarh 2',
        ];
    

    getClientList () 
        return Observable.of(this.clientList);
    
;

在我们调用服务的get函数的组件中-

this.clientListService.getClientList().subscribe(res => this.clientList = res);

【讨论】:

干得好@Anirban,也只能返回(this.clientList);【参考方案6】:

请注意,您正在使用 Observable#map 将您的基本 Observable 发出的原始 Response 对象转换为 JSON 响应的解析表示。

如果我理解正确,你想再次map。但这一次,将原始 JSON 转换为 Model 的实例。所以你会做这样的事情:

http.get('api/people.json')
  .map(res => res.json())
  .map(peopleData => peopleData.map(personData => new Person(personData)))

因此,您从一个发出 Response 对象的 Observable 开始,将其转换为发出该响应的解析 JSON 的对象的 observable,然后将其转换为另一个将原始 JSON 转换为你的模型数组。

【讨论】:

以上是关于从 Angular 2 服务创建和返回 Observable的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2 Setter 和 Getter

为 Angular 2+ 创建一个装饰器,返回服务 http 调用的最后结果

Angular 2 - 直接从 Observable 返回数据

在 Angular 服务中 HTTP.get 之后从 firebase 返回的重复数组

根据从 Angular 组件返回的项目宽度动态设置容器的宽度

Angular 2 从 RxJs 订阅返回数据