【问题标题】:Creating and returning Observable from Angular 2 Service从 Angular 2 服务创建和返回 Observable
【发布时间】: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 并将其返回?

【问题讨论】:

    标签: angular observable


    【解决方案1】:

    更新:2016 年 9 月 24 日 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 调用中获取数据的 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 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 =&gt; this.people = people);

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

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

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

    【讨论】:

    • 我尝试了这种方法,但得到“不能将'new'与类型缺少调用或构造签名的表达式一起使用”-错误​​。有人知道该怎么做吗?
    • @Spock 自从这个原始问题以来,规范似乎已经更新。您不再需要可观察对象的“新”,因为它为您执行此操作。只需删除新的并让我知道会发生什么。我现在正在搞砸一些东西,如果它也适合你,我会更新这个答案
    • 不鼓励将EventEmitter 用于除@Output() 之外的任何内容。另见stackoverflow.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://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

      【讨论】:

        【解决方案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 转换为你的模型数组。

        【讨论】:

          猜你喜欢
          • 2017-03-24
          • 2018-07-14
          • 1970-01-01
          • 2021-09-24
          • 2016-10-18
          • 2017-06-24
          • 2016-10-25
          • 1970-01-01
          • 2016-12-18
          相关资源
          最近更新 更多