在Angular 2中,使用observable获取一次数据

Fetch data once with Observables in Angular 2

本文关键字:数据 一次 获取 observable Angular 使用      更新时间:2023-09-26

我有一个服务,它在我的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();               
}   

还是同样的问题。任何建议,哪些运营商我必须使用只获取数据一次?

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

您还可以从SomeService中公开始终最新的customers字段,用于只读目的(如在模板中显示),如下所示:

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

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

如果你想让多个子对象订阅同一个可观察对象,但只执行一次可观察对象,你可以这样做:

注意,这确实遵循了可观察对象的设计,因为我们是在服务层执行可观察对象(observable . frompromise (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;
 }

share操作符允许对多个观察者使用同一个流的结果。它可能是好的,但是你每次调用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);
        })
    }
}

无需自定义实现。管道可以做到这一点:

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

我在这里做了几件事:

  1. 添加shareReplay(1)管道,以确保请求只完成一次(只需要回答问题)
  2. 删除map,使get呼叫类型为
  3. $后缀方法名,表示返回Observable