从 Angular 2 服务创建和返回 Observable
Posted
技术标签:
【中文标题】从 Angular 2 服务创建和返回 Observable【英文标题】:Creating and returning Observable from Angular 2 Service 【发布时间】:2016-02-13 23:54:58 【问题描述】:这更像是一个“最佳实践”问题。共有三个玩家:Component
、Service
和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
可以在 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 => 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<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
对象塞入我们的观察者。接下来,我们将从组件中的调用方访问此类型!
在组件中:
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<people>
来初始化我们的_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+ 创建一个装饰器,返回服务 http 调用的最后结果
Angular 2 - 直接从 Observable 返回数据
在 Angular 服务中 HTTP.get 之后从 firebase 返回的重复数组