我有一项服务,很多我的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();
}
仍然是同一个问题。操作员必须使用的任何提议只能获取一次数据吗?
答案 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));
}
我在这里做了几件事:
shareReplay(1)
管道,以确保请求仅执行一次(仅需要回答问题)map
并输入 get
调用$
的后缀方法名称表示返回了 Observable