How to use BehaviourSubjects to share data from API call between components in Angular?

蓝咒 提交于 2020-04-07 08:09:06

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!