Angular 2サービスからObservableを作成して返す


132

これはより「ベストプラクティス」の質問です。3人のプレーヤーがあります:a Component、a Service、a ModelComponent呼び出しているServiceデータベースから取得するデータを。Service使用しています:

this.people = http.get('api/people.json').map(res => res.json());

を返すObservable

Componentちょうどを購読することができObservable

    peopleService.people
        .subscribe(people => this.people = people);
      }

ただし、私が本当に欲しいのは、がデータベースから取得したデータから作成されServiceArray of Modelオブジェクトを返すことですService。がComponentこの配列をsubscribeメソッドで作成するだけでよいことに気付きましたが、サービスがそれを実行してで使用できるようにすると、よりクリーンになると思いますComponent

どのようにしてその配列を含むService新しいを作成しObservable、それを返すことができますか?

回答:


159

更新:2016年9月24日Angular 2.0安定

この質問はまだ多くのトラフィックを得るので、更新したかったのです。Alpha、Beta、および7つのRC候補からの変更の狂気のため、SOの回答の更新は、それらが安定するまで停止しました。

これは、SubjectおよびReplaySubjectを使用する場合に最適です。

私は個人的に使用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

驚くばかり!件名を閉じても、最後に読み込んだもので返信しました。

もう1つは、そのhttp呼び出しにサブスクライブして応答を処理する方法です。マップは応答を処理するのに最適です。

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呼び出しからデータを取得するオブザーバブルです。

OKそれは素晴らしいですが、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値をプッシュできるようにするを作成します。ではアルファ45あなたがそれを変換する必要がありtoRx()そうで、しかし、私は彼らがそれを取り除くために働いていた知っているアルファ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、はい、それは今です...当時、それは全体的にEventEmitterでしたが、Rx Observablesで標準化されています。私の観察可能な例はまだ動作しますが、あなたは私が与えた持つEventEmitterの例を使用するつもりだった場合、私は直接科目を使用することをお勧め:github.com/Reactive-Extensions/RxJS/blob/master/doc/api/...
デニスSmolek

1
編集用@maxisamのおかげで、答えは/観測のための「新しい」を削除アルファに相対的であるものの、今は正しいです
デニスSmolek

29

これは、独自のObservableを作成して使用する方法のAngular2ドキュメントの例です。

サービス

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とSubjectを使用しないという利点があると思います。

それで、これが私のアプローチです。私たちは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();


          });
   });
}

OK、ご覧のとおり、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;
   });
}

それ_peopleObservableObservable<people>から返すことで初期化しますPeopleService。次に、このプロパティをサブスクライブします。最後に、this.peopledata(people)応答を設定します。

この方法でサービスを設計することには、典型的なサービスに比べて1つの大きな利点があります。それは、map(...)およびcomponent: "subscribe(...)"パターンです。現実の世界では、jsonをクラスのプロパティにマップする必要があり、場合によってはそこでカスタムスタッフを実行します。したがって、このマッピングはサービスで発生する可能性があります。また、通常、サービス呼び出しは1回ではなく、おそらくコードの他の場所で使用されるため、一部のコンポーネントでそのマッピングを再度実行する必要はありません。さらに、人々に新しいフィールドを追加したらどうなるでしょうか?...


書式設定はサービス内にあることに同意し、標準のObservableメソッドも投稿しましたが、サービス内のサブジェクトの利点は、他の関数がその上でトリガーできることです。あなたは常にだけにして私が観察方法を使用したい直接HTTP呼び出しを必要としている場合...
デニスSmolek

9

service.tsファイル-

a。オブザーバブルから/をインポートする
b。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#mapを使用してResponse、ベース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のオブジェクトを発行するオブザーバブルに変換し、次に、そのraw JSONをモデルの配列に変換するさらに別のオブザーバブルに変換しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.