首页 文章

从Angular 2服务创建和返回Observable

提问于
浏览
112

这更像是一个"best practices"问题 . 有三个玩家: ComponentServiceModel . 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 并返回该数组?

6 回答

  • 18

    UPDATE: 9/24/16 Angular 2.0 Stable

    这个问题仍然有很多流量,所以,我想更新它 . 随着来自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%确定,你只需要做一次电话怎么办?留下开放的主题和可观察的东西并不总是 "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调用中获取数据的observable .

    OK thats great but I want an angular2 service!

    我接到你了:

    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);
        }
    
     }
    

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

    Original Answer

    我认为这是使用Observable SubjectAngular2 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 流,以防我需要从其他组件访问它,但我可以看到两种方式都工作...

    这是一个使用事件 Launcher 显示基本服务的plunker:Plunkr

  • 7

    这是Angular2 docs中有关如何创建和使用自己的Observable的示例:

    The Service

    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)
      }
    }
    

    The Component

    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

  • 13

    我想补充说,如果创建的对象是静态的,而不是来自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());
          }
        }
    

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

  • 147

    我参加派对有点晚了,但我认为我的方法的优势在于它没有使用EventEmitters和Subjects .

    所以,这里's my approach. We can' t远离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 对象塞入我们的观察者 . 接下来我们将从Component中的调用者访问此类型!

    在组件中:

    private _peopleObservable: Observable<people>;
    
    constructor(private peopleService: PeopleService){}
    
    getPeople(hairColor:string) {
       this._peopleObservable = this.peopleService.people(hairColor);
    
       this._peopleObservable.subscribe((data) => {
          this.people = data;
       });
    }
    

    我们初始化 _peopleObservablePeopleService 返回 Observable<people> . 然后,我们订阅了这个属性 . 最后,我们将 this.people 设置为我们的数据( people )响应 .

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

  • 27

    请注意,您正在使用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)))
    

    所以,你开始使用一个Observable发出 Response 对象,将其转换为一个observable,它发出该响应的解析JSON的对象,然后将其转换为另一个observable,将原始JSON转换为模型数组 .

  • 4

    在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);
    

相关问题