从Angular 2服务创建和返回Observable


132

这更多是一个“最佳实践”问题。一共有三个玩家:a Component,a Service和a Model。在Component被调用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可以在subscription方法中创建此数组,但是我认为,如果服务执行此操作并将其提供给,它将更干净Component

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

Answers:


158

更新:9/24/16 Angular 2.0稳定

这个问题仍然吸引了大量流量,因此,我想对其进行更新。由于Alpha,Beta和7个RC候选人的变化而导致的疯狂,我停止更新我的SO答案,直到它们变得稳定为止。

这是使用SubjectsReplaySubjects的完美案例

个人更喜欢使用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调用中获取数据。

好的,那很好,但是我想要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,您的不同服务功能便可以轻松执行。

如果您想直接从通话中返回可观察对象,则可以执行以下操作:

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 => this.people = people);

并弄乱您的服务中的通话结果。

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

这是一个通过事件发射器显示基本服务的插件Plunkr


我尝试了这种方法,但收到“无法对类型缺少调用或构造签名的表达式不能使用'new'”-错误。有人知道该怎么做吗?
Spock

3
自从最初的问题以来,@ Spock规范似乎已经更新。您不再需要可观察的“新”功能,因为它可以为您完成此操作。只需删除新的,让我知道会发生什么。我现在正在弄乱一些东西,如果它也对您
有用,

1
使用EventEmitter任何东西,但@Output()不鼓励。另请参见stackoverflow.com/questions/34376854/...
君特Zöchbauer

@GünterZöchbauer,是的,现在...当时将是EventEmitters,但自那时以来,它们已经在Rx Observables上进行了标准化。我可观察例子仍然有效,但如果你要使用我给EventEmitter例子,我建议直接使用主题:github.com/Reactive-Extensions/RxJS/blob/master/doc/api/...
丹尼斯Smolek

1
@maxisam感谢您的编辑,尽管答案是相对于Alpha而言/相对于为Observable删除了“新”现在是正确的
Dennis Smolek

29

这是来自Angular2文档的一个示例,说明如何创建和使用自己的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


18

我想补充一点,如果所创建的对象是静态的,并且不通过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.xx映射需要使用pipe()完成,如此处所述(https://stackoverflow.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 : //stackoverflow.com/a/35219772/986160


17

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

所以,这是我的方法。我们无法摆脱subscribe(),我们也不想这么做。因此,我们的服务将Observable<T>与载有我们宝贵货物的观察员一起返回。从调用者处,我们将初始化一个变量,Observable<T>它将获取服务的Observable<T>。接下来,我们将订阅该对象。最后,您得到了“ 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”类型。该方法的签名甚至可以这样说!我们将_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;
   });
}

我们_peopleObservable通过Observable<people>从中返回来初始化我们的代码PeopleService。然后,我们订阅此属性。最后,我们设置this.people为data(people)响应。

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


我同意格式应该在服务中,并且我也发布了一个标准的Observable方法,但是服务中主题的优点是可以触发其他功能。如果你总是只需要直接HTTP调用,然后我会用可观察的方法..
丹尼斯Smolek

9

在service.ts文件中-

一个。从可观察的/ 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);
foo-baar,

7

请注意,您正在使用Observable#mapResponse基础Observable发出的原始对象转换为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转换为模型数组的另一个Observable 。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.