web-dev-qa-db-ja.com

Angular 2のオブザーバブルでデータを1回フェッチする

私は多くのAngular 2コンポーネントから数回使用されているサービスを持っています。WebAPIから顧客データをフェッチし、Observableを返します。

getCustomers() {
   return this.http
        .get(this.baseURI + this.url)
        .map((r: Response) => {
            let a = r.json() as Customer[];                       
            return a;                         
        });               
}    

このサービスを自分のルートコンポーネントと、Observableにサブスクライブしている顧客にアクセスしたいすべてのコンポーネントに挿入します。

this.customerService.getCustomers().subscribe(v => this.items = v);

ただし、私のObservableをサブスクライブするすべてのコンポーネントは、HTTPリクエストの別の実行を引き起こします。ただし、データを1回だけフェッチするだけで十分です。 share()を試しても問題は解決しません。

getCustomers() {
   return this.http
        .get(this.baseURI + this.url)
        .map((r: Response) => {
            let a = r.json() as Customer[];                       
            return a;                         
        }).share();               
}   

まだ同じ問題。データを1回だけ取得するために使用する必要がある演算子はありますか?

11
David

1)ダウンロードしたデータをサービスに保存するだけです:

export class CustomersService {
  protected _customers: Array<Customer>;

  constructor(public http: Http) {}

  public getCustomers(): Observable<Array<Customer>> {
    return new Observable(observer => {
      if (this._customers) {
        observer.next(this._customers);
        return observer.complete();
      }
      this.http
        .get(this.baseURI + this.url)
        .map((r: Response) => (r.json() as Array<Customer>))
        .subscribe((customers: Array<Customer>) => {
          this._customers = customers;
          observer.next(this.customers);
          observer.complete();
        });
    });
  }
}

2)refreshパラメータを取るより短いアプローチ:

export class CustomersService {
  protected _customers: Array<Customer>;

  constructor(public http: Http) {}

  public getCustomers(refresh?: boolean): Observable<Array<Customer>> {
    if (!refresh && this._customers) {
      return Observable.of(this._customers);
    }
    return this.http
            .get(this.baseURI + this.url)
            .map((c: Response) => (c.json() as Array<Customer>))
            .do((customers: Array<Customer>) => {
                this._customers = customers;
            });
    });
  }
}

3)ReplaySubjectを利用する:

export class CustomersService {
  protected _customers$: ReplaySubject<Array<Customer>> = new ReplaySubject(1);
  protected _customersInitialized: boolean;

  constructor(public http: Http) {}

  public getCustomers(refresh?: boolean): Observable<Array<Customer>> {
    if (refresh || !this._customersInitialized) {
      this._customersInitialized = true;
      this.http
        .get(this.baseURI + this.url)
        .map((c: Response) => (c.json() as Array<Customer>))
        .subscribe((customers: Array<Customer>) => {
          this._customers$.next(customers);
        });
    }
    return this._customers$.asObservable().skip(+refresh).distinctUntilChanged();
  }
}

その後:

this.customersService.getCustomers()
    .subscribe(customers => this.customers = customers);

次のように、読み取り専用の目的(テンプレートでの表示など)のために、customersから常に最新のSomeServiceフィールドを公開することもできます。

public get customers(): ReadonlyArray<Customer> {
  return this._customers;
}
15
Daniel Kucal

親コンテナーを作成し、データを1回フェッチして、@ Inputを使用してそれを子コンポーネントに渡します。

親:

@Component({
    selector: 'BarFooHttpCaller',
    template: ´<child *ngIf="data.length > 0" [data]></child>´
})

class BarFooHttpCaller {
    private data: any;
    constructor(private foobar:Foobar) {
        this.data = {};
    }

    ngOnInit() {
        this.foobar.getCustomers().subscribe(() => {       
            console.log('httpdone') 
        });
        this.foobar.dataStream.subscribe((data) => {
            console.log('new data', data);
            this.data = data;
        })
    }
}

子:

import { Component, Input } from '@angular/core';

@Component({
    selector: 'child',
    template: ´<div>{{data}}</div>´
})

export class Child {
    @Input() data: any;

}
3
Yoann

複数の子に同じオブザーバブルをサブスクライブさせたいが、オブザーバブルを実行できるのは、以下を実行できる場合のみです。

サービスレイヤー(Observable.fromPromis(stream.toPromise()))でオブザーバブルを実行しているときに、サブスクライブするコンポーネントから実行する必要があるため、これはオブザーバブルの設計に準拠していることに注意してください。ビュー https://詳細については、www.bennadel.com/blog/3184-creating-leaky-abstractions-with-rxjs-in-angular-2-1-1.htm をご覧ください。

  //declare observable to listen to
  private dataObservable: Observable<any>;

  getData(slug: string): Observable<any> {

    //If observable does not exist/is not running create a new one
    if (!this.dataObservable) {

        let stream = this.http.get(slug + "/api/Endpoint")
            .map(this.extractData)
            .finally(() => {
                //Clear the observable now that it has been listened to
                this.staffDataObservable = null;
            });

        //Executes the http request immediately
        this.dataObservable = Observable.fromPromise(stream.toPromise());

    }        

    return this.staffDataObservable;
 }
2
Adam Hannigan

共有演算子は、複数のオブザーバーで同じストリームの結果を使用する可能性を与えます。それは良いかもしれませんが、getCustomers()を呼び出すたびに新しい監視可能なストリームを生成します。このストリームを複数回サブスクライブしていないので、share()を呼び出す意味はありません。

複数のオブザーバーとデータを共有したいが、1回のhttp呼び出しのみを行う場合は、データを含む2番目のストリームを作成し、httpストリームでフィードするだけです。その後、すべてのコンポーネントがサブスクライブできます。

コードはそのようなものである可能性があります

@Injectable()
class FooBar {

    public dataStream:Subject<any> = new Subject();

    constructor(private http:Http) {}

    public getCustomers() {
        return this.http
        .get(this.baseURI + this.url)
        .map((response:Response) => response.json())
        .map((data) => {
            this.dataStream.next(data); 
            return data;
        })
    }

}


@Component({})
class BarFooHttpCaller {
    constructor(private foobar:Foobar) {}

    ngOnInit() {
        this.foobar.getCustomers().subscribe(() => { console.log('http done') });
        this.foobar.dataStream.subscribe((data) => {
            console.log('new data', data);
        })
    }
}

@Component({})
class OtherBarFoo {
    constructor(private foobar:Foobar) {}

    ngOnInit() {
        this.foobar.dataStream.subscribe((data) => {
            console.log('new data', data);
        })
    }
}
1
Polochon