问题
I am currently building an Angular application where I make a request to an api, and I map the repsonse to two different arrays. I can use this data in my app.components.ts
but I will make new components for what I need. How can I share the data between components to ensure that the components always have the latest data because I will also need to periodically call the API.
I've seen some answers on SO and some youtube videos but I'm just not fully understanding it.
The code of my service is
url = 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson';
private _earthquakePropertiesSource = new BehaviorSubject<Array<any>>([]);
private _earthquakeGeometrySource = new BehaviorSubject<Array<any>>([]);
constructor(private readonly httpClient: HttpClient) {
}
public getEarthquakeData(): Observable<{ properties: [], geometries: []}> {
return this.httpClient.get<any>(this.url).pipe(
// this will run when the response comes back
map((response: any) => {
return {
properties: response.features.map(x => x.properties),
geometries: response.features.map(x => x.geometry)
};
})
);
}
It is being used in my app.component.ts
as follows:
properties: Array<any>;
geometries: Array<any>;
constructor(private readonly earthquakeService: EarthquakeService) {
}
ngOnInit() {
this.earthquakeService.getEarthquakeData().subscribe(data => {
this.properties = data.properties;
this.geometries = data.geometries;
this.generateMapData();
});
}
generateMapData() {
for (const g of this.geometries) {
const tempData: any = {
latitude: g.coordinates[0],
longitude: g.coordinates[1],
draggable: false,
};
this.mapData.push(tempData);
}
Any help would be greatly appreciated.
回答1:
This is an answer describing how it can be done using pure RxJS. Another alternative is to use NgRx.
Firstly, you have set up two subjects. The intention being that all components will subscribe to them and receive the latest data when it is refreshed?
You should use ReplaySubject
instead of BehaviorSubject
though, since you don't have any initial state. And since the data comes back as one thing, I would use one subject.
Firstly, I am going to declare an interface to make it easier to talk about the data types.
earthquake-data.ts
export interface EarthquakeData {
// TODO: create types for these
geometries: any[];
properties: any[];
}
In your service, you can separate the retrieval and the notifications by exposing the data via your own methods.
earthquake.service.ts
url = 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson';
private _earthquakeData$ = new ReplaySubject<EarthquakeData>(1);
constructor(private readonly httpClient: HttpClient) {}
getEarthquakeData(): Observable<EarthquakeData> {
// return the subject here
// subscribers will will notified when the data is refreshed
return this._earthquakeData$.asObservable();
}
refreshEarthquakeData(): Observable<void> {
return this.httpClient.get<any>(this.url).pipe(
tap(response => {
// notify all subscribers of new data
this._earthquakeData$.next({
geometries: response.features.map(x => x.geometry),
properties: response.features.map(x => x.properties)
});
})
);
}
So now, all components that want to receive data will subscribe to one method:
private destroyed$ = new Subject();
ngOnInit()
this.earthquakeService.getEarthquakeData().pipe(
// it is now important to unsubscribe from the subject
takeUntil(this.destroyed$)
).subscribe(data => {
console.log(data); // the latest data
});
}
ngOnDestroy() {
this.destroyed$.next();
this.destroyed$.complete();
}
And you can refresh the data wherever you want to:
refreshData() {
this.refreshing = true;
this.earthquakeService.refreshEarthquakeData().subscribe(() => {
this.refreshing = false;
});
}
DEMO: https://stackblitz.com/edit/angular-uv7j33
回答2:
You can include a property on the service that will hold this data, and subscribe to it instead. I'm assuming you'll have a timed interval checking for new responses - which can then just update the value of the property in the service.
export interface earthQuakeResponse {
properties: Array<any>
geometries: Array<any>
}
export class EarthQuakeService {
private _earthQuakeResponse = new BehaviorSubject<earthQuakeResponse>([]);
readonly earthQuakeResponse = this._earthQuakeResponse.asObservable();
public getEarthquakeData(): Observable<earthQuakeResponse> {
return this.earthQuakeResponse;
}
//call this method when you want to update your data
private updateData() {
this.httpClient.get<any>(this.url).subscribe(
response => {
this._earthQuakeResponse.next({
properties: response.features.map(x => x.properties),
geometries: response.features.map(x => x.geometry)
});
});
}
}
回答3:
The simple way to go about this, would be to use BehaviorSubject
. The documentation on this is comprehensive, I'm sure you can find it.
To handle complex state in large applications, people use Redux. For Angular, there is NgRx.
If updating state requires you to call an API as a side effect, use ngrx/effects
https://ngrx.io/guide/effects
回答4:
To share information between components you can use a behaviorSubject in a service that will be used in your different components.
The BehaviorSubject has the characteristic that it stores the “current” value, the last value, that needs to be shared with other components.
Its particularity is:
need an initial value
const subject = new MyBehaviorSubject('initialValue');
return the last value of the subject
You can retrieve the last value with getValue() method ( non observable)
subject.getValue()
you can subscribe to it:
subject.subscribe(console.log);
update the value with next()
subject.next('New value');
I give you an example: in my service:
private isOpen = new BehaviorSubject<boolean>(false);
public getNavBarOpen(): Observable<boolean> {
return this.isOpen.asObservable();
}
setNavBarOpen(status: boolean): void {
this.isOpen.next(status);
}
in my component:
if I want to update the value :
this.myService.setNavBarOpen(true);
If i want to get the value :
this.myService.getNavBarOpen().subscribe()
回答5:
The service method doesn't need return an Observable:
public getEarthquakeData(): Observable<{ properties: [], geometries: []}> {
return this.httpClient.get<any>(this.url).pipe(
// this will run when the response comes back
tap((response: any) => {
_earthquakePropertiesSource.next(response.features.map(x => x.properties));
_earthquakeGeometrySource.next(response.features.map(x => x.geometry));
})
});
And the component:
ngOnInit() {
combineLatest(
this.earthquakeService._earthquakePropertiesSource,
this.earthquakeService._earthquakeGeometrySource
).subscribe(data => {
this.properties = data[0];
this.geometries = data[1];
this.generateMapData();
});
}
回答6:
Edit 1
Service:
url = 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson';
properties = new BehaviorSubject<Array<any>>([]);
geometries = new BehaviorSubject<Array<any>>([]);
constructor(private readonly httpClient: HttpClient) {
loadEarthquakeData().
}
public loadEarthquakeData(): Observable<{ properties: [], geometries: []}> {
return this.httpClient.get<any>(this.url).pipe(
tap((response: any) => {
this.properties.next(response.features.map(x => x.properties);
this.geometries.next(response.features.map(x => x.geometry));
})
).toPromise();
}
Component:
private _subscription: Subscription;
constructor(private readonly earthquakeService: EarthquakeService) {
}
ngOnInit() {
this.generateMapData();
}
ngOnDestroy() {
if (this._subscription) {
this._subscription.unsubscribe();
}
}
generateMapData() {
this._subscription = this.earthquakeService.geometries.subscribe(geometries => {
for (const g of this.earthquakeService.geometries.getValue()) {
const tempData: any = {
latitude: g.coordinates[0],
longitude: g.coordinates[1],
draggable: false,
};
this.mapData.push(tempData);
}
});
}
Original
For that, you need Angular Services
They are singletons that can act like a shared state. What you want to do is to store your data inside the service, and then call the service from both of your components and listen to the service's BehaviorSubject.
来源:https://stackoverflow.com/questions/60542279/how-to-use-behavioursubjects-to-share-data-from-api-call-between-components-in-a