I have a service, what is used several times from a lot of my Angular 2 components. It fetches customer data from a Web API and returns an Observable:
getCustomers() {
return this.http
.get(this.baseURI + this.url)
.map((r: Response) => {
let a = r.json() as Customer[];
return a;
});
}
I inject this service in my root component, and in every component that wants to access the customers I just subscribe to that Observable:
this.customerService.getCustomers().subscribe(v => this.items = v);
However, every component who subscribes to my Observable causes another execution of the HTTP-request. But to fetch the data only once is enough. If I try share(), it does not solve my problem:
getCustomers() {
return this.http
.get(this.baseURI + this.url)
.map((r: Response) => {
let a = r.json() as Customer[];
return a;
}).share();
}
Still the same issue. Any proposals which operators I have to use to only fetch data once?
next() The subject next method is used to send messages to an observable which are then sent to all angular components that are subscribers (a.k.a. observers) of that observable.
Promises deal with one asynchronous event at a time, while observables handle a sequence of asynchronous events over a period of time. Emit multiple values over a period of time. Emit a single value at a time. Are lazy: they're not executed until we subscribe to them using the subscribe() method.
the Promise is always asynchronous, while the Observable can be either asynchronous or synchronous, the Promise can provide a single value, whereas the Observable is a stream of values (from 0 to multiple values), you can apply RxJS operators to the Observable to get a new tailored stream.
1) You can simply save downloaded data in your service:
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) Shorter approach taking refresh
parameter:
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) Taking advantage of 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();
}
}
And then:
this.customersService.getCustomers()
.subscribe(customers => this.customers = customers);
You can also expose the always up-to-date customers
field from SomeService
for read only purposes (like displaying in the templates) this way:
public get customers(): ReadonlyArray<Customer> {
return this._customers;
}
I would create a parent container, fetch the data once, and pass it to child components using @Input.
Parent:
@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;
})
}
}
Child:
import { Component, Input } from '@angular/core';
@Component({
selector: 'child',
template: ´<div>{{data}}</div>´
})
export class Child {
@Input() data: any;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With