使用Angular 2中的Observables获取数据一次

时间:2016-11-10 14:33:23

标签: javascript angular rxjs observable

我有一项服务,很多我的Angular 2组件使用过多次。它从Web API获取客户数据并返回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请求的执行。但是只获取一次数据就足够了。 如果我尝试share(),它不能解决我的问题:

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

仍然是同一个问题。操作员必须使用的任何提议只能获取一次数据吗?

5 个答案:

答案 0 :(得分:11)

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

答案 1 :(得分:3)

我会创建一个父容器,获取一次数据,然后使用@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;

}

答案 2 :(得分:2)

如果您希望多个子节点订阅相同的observable,但只有在您执行以下操作后才执行observable。

请注意,这确实遵循了observable的设计,因为我们正在执行服务层中的observable(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;
 }

答案 3 :(得分:1)

共享运算符可以为多个观察者使用相同的流结果。这可能很好,但每次调用getCustomers()时都会生成一个新的可观察流,因为您没有多次订阅此流,所以没有必要调用share()

如果您想与多个观察者共享数据但只进行一次http调用,您只需创建第二个流,由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);
        })
    }
}

答案 4 :(得分:0)

无需自定义实现。管道可以解决问题:

getCustomers$(): Observable<Customer> {
   return this.http
        .get<Customer>(this.baseURI + this.url)
        .pipe(shareReplay(1));               
}    

我在这里做了几件事:

  1. 添加 shareReplay(1) 管道,以确保请求仅执行一次(仅需要回答问题)
  2. 删除 map 并输入 get 调用
  3. 带有 $ 的后缀方法名称表示返回了 Observable