web-dev-qa-db-ja.com

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

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

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

Observableを返します。

Componentは、単にObservableにサブスクライブできます。

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

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

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

124
Joseph Genchik

更新日:9/24/16 Angular 2.0安定

この質問はまだ大量のトラフィックを受け取るので、私はそれを更新したいと思いました。 Alpha、Beta、および7つのRC候補からの変更の狂気のために、私はSO回答が安定するまで更新を中止しました。

これは Subjects および ReplaySubjects を使用するのに最適なケースです。

I 個人的には、遅くても新しい購読者がアタッチしたときに最後に格納された値が渡されるようにするので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

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

もう1つのことは、その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呼び出しからデータを取得する観測量です。

[OK]をクリックします。

見つけた:

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 またはAngular2EventEmitterを使ったユースケースだと思います。

あなたのサービスであなたはそれに値をプッシュすることを可能にする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を持っています。

呼び出しから直接観測量を返したい場合は、次のようにします。

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

151
Dennis Smolek

これは、 Angular 2 docs からの例です。独自のObservablesを作成して使用する方法は次のとおりです。

サービス

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

27
tibbus

私は作成されたオブジェクトが静的であり、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

18

パーティーには少し時間がかかりますが、私のアプローチには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();


          });
   });
}

わかりますか。ご覧のとおり、 "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;
   });
}

PeopleServiceからその_peopleObservableを返すことで、Observable<people>を初期化します。その後、このプロパティを購読します。最後に、this.peopleをdata(people)応答に設定します。

このようにサービスを設計することには、典型的なサービスよりも大きな利点が1つあります。それは、map(...)およびcomponent: "subscribe(...)"パターンです。現実の世界では、クラスの中で自分のプロパティにJSONをマッピングする必要があります。そして時には、そこでカスタムのことをすることもあります。そのため、このマッピングは私たちのサービスで発生する可能性があります。そして、通常、私たちのサービスコールは一度だけ使われるのではなく、おそらく、私たちのコードの他の場所でも、そのマッピングを何らかのコンポーネントで実行する必要はないでしょう。さらに、人々に新しい分野を追加したらどうなるでしょうか。

15
LargeDachshund

ベースObservableが発行する生のResponseオブジェクトをJSONレスポンスの解析済み表現に変換するために Observable#map を使用していることに注意してください。

私があなたを正しく理解したならば、あなたは再び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をあなたのモデルの配列に変えるもう一つのobservableに変えました。

7
julioolvr

Service.tsファイルで -

a。オブザーバブル/ ofから 'of'をインポート
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);
4
Anirban Bhadra