从 Angular 2 服务创建和返回 Observable
Creating and returning Observable from Angular 2 Service
这更像是一个 "best practices" 问题。一共有三个玩家:一个Component
,一个Service
和一个Model
。 Component
正在调用 Service
从数据库中获取数据。 Service
正在使用:
this.people = http.get('api/people.json').map(res => res.json());
到return一个Observable
。
Component
可以订阅 Observable
:
peopleService.people
.subscribe(people => this.people = people);
}
但是,我真正想要的是 Service
到 return 一个 Array of Model
对象,这些对象是根据 Service
从数据库检索的数据创建的。我意识到 Component
可以在订阅方法中创建这个数组,但我认为如果服务这样做并使其对 Component
.
可用会更清晰
Service
如何创建一个包含该数组的新 Observable
,以及 return 那个?
更新:2016 年 9 月 24 日 Angular 2.0 稳定
这个问题仍然有很多流量,所以,我想更新一下。由于 Alpha、Beta 和 7 名 RC 候选人的疯狂变化,我停止更新我的 SO 答案,直到它们稳定下来。
这是使用 Subjects and 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% 确定只需要调用一次呢?留下开放的主题和可观察对象并不好,但总是 "What If?"
这就是 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 你可以简单地 return EvenEmitter
。
class EventService {
_emitter: EventEmitter = new EventEmitter();
rxEmitter: any;
constructor() {
this.rxEmitter = this._emitter.toRx();
}
doSomething(data){
this.rxEmitter.next(data);
}
}
这种方式具有单个 EventEmitter
,您的不同服务功能现在可以推送到它上面。
如果您想 return 直接从调用中观察到一个可观察对象,您可以这样做:
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
我想补充一点,如果创建的对象是静态的并且不是通过 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() 完成,如下所述 ():
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
}
}
来自对我关于观察者和静态数据的问题的回答:
这是来自 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
我来晚了一点,但我认为我的方法的优点是它没有使用 EventEmitter 和 Subjects。
所以,这是我的方法。我们无法摆脱 subscribe(),我们也不想。本着这种精神,我们的服务将 return 和 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();
});
});
}
好的,如您所见,我们正在 returning 类型 "people" 的 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;
});
}
我们通过 return 从我们的 PeopleService
中获取 Observable<people>
来初始化我们的 _peopleObservable
。然后,我们订阅这个属性。最后,我们将 this.people
设置为我们的数据(people
)响应。
以这种方式构建服务与典型服务相比有一个主要优势:map(...) 和组件:"subscribe(...)" 模式。在现实世界中,我们需要将 json 映射到 class 中的属性,有时我们会在那里做一些自定义的事情。所以这个映射可以发生在我们的服务中。而且,通常情况下,因为我们的服务调用不会被使用一次,而且可能会在我们代码的其他地方使用,所以我们不必再次在某些组件中执行该映射。而且,如果我们给people增加一个新的字段呢?....
请注意,您正在使用 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,然后将其转换为另一个对象observable 将原始 JSON 转换为模型数组。
在 service.ts 文件中 -
一个。从 observable/of
导入 'of'
b.创建一个 json 列表
C。 return json 对象使用 Observable.of()
前任。 -
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);
这更像是一个 "best practices" 问题。一共有三个玩家:一个Component
,一个Service
和一个Model
。 Component
正在调用 Service
从数据库中获取数据。 Service
正在使用:
this.people = http.get('api/people.json').map(res => res.json());
到return一个Observable
。
Component
可以订阅 Observable
:
peopleService.people
.subscribe(people => this.people = people);
}
但是,我真正想要的是 Service
到 return 一个 Array of Model
对象,这些对象是根据 Service
从数据库检索的数据创建的。我意识到 Component
可以在订阅方法中创建这个数组,但我认为如果服务这样做并使其对 Component
.
Service
如何创建一个包含该数组的新 Observable
,以及 return 那个?
更新:2016 年 9 月 24 日 Angular 2.0 稳定
这个问题仍然有很多流量,所以,我想更新一下。由于 Alpha、Beta 和 7 名 RC 候选人的疯狂变化,我停止更新我的 SO 答案,直到它们稳定下来。
这是使用 Subjects and 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% 确定只需要调用一次呢?留下开放的主题和可观察对象并不好,但总是 "What If?"
这就是 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 你可以简单地 return EvenEmitter
。
class EventService {
_emitter: EventEmitter = new EventEmitter();
rxEmitter: any;
constructor() {
this.rxEmitter = this._emitter.toRx();
}
doSomething(data){
this.rxEmitter.next(data);
}
}
这种方式具有单个 EventEmitter
,您的不同服务功能现在可以推送到它上面。
如果您想 return 直接从调用中观察到一个可观察对象,您可以这样做:
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
我想补充一点,如果创建的对象是静态的并且不是通过 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() 完成,如下所述 (
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
}
}
来自对我关于观察者和静态数据的问题的回答:
这是来自 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
我来晚了一点,但我认为我的方法的优点是它没有使用 EventEmitter 和 Subjects。
所以,这是我的方法。我们无法摆脱 subscribe(),我们也不想。本着这种精神,我们的服务将 return 和 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();
});
});
}
好的,如您所见,我们正在 returning 类型 "people" 的 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;
});
}
我们通过 return 从我们的 PeopleService
中获取 Observable<people>
来初始化我们的 _peopleObservable
。然后,我们订阅这个属性。最后,我们将 this.people
设置为我们的数据(people
)响应。
以这种方式构建服务与典型服务相比有一个主要优势:map(...) 和组件:"subscribe(...)" 模式。在现实世界中,我们需要将 json 映射到 class 中的属性,有时我们会在那里做一些自定义的事情。所以这个映射可以发生在我们的服务中。而且,通常情况下,因为我们的服务调用不会被使用一次,而且可能会在我们代码的其他地方使用,所以我们不必再次在某些组件中执行该映射。而且,如果我们给people增加一个新的字段呢?....
请注意,您正在使用 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,然后将其转换为另一个对象observable 将原始 JSON 转换为模型数组。
在 service.ts 文件中 -
一个。从 observable/of
导入 'of'
b.创建一个 json 列表
C。 return json 对象使用 Observable.of()
前任。 -
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);