如何使用 BehaviourSubjects 在 Angular 中的组件之间共享来自 API 调用的数据?

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

我目前正在构建一个 Angular 应用程序,我在其中向 api 发出请求,并将响应映射到两个不同的数组。我可以在我的 app.components.ts 中使用这些数据,但我会根据需要制作新组件。如何在组件之间共享数据以确保组件始终具有最新数据,因为我还需要定期调用 API.

我在 SO 和一些 youtube 视频上看到了一些答案,但我并不完全理解它。

我的服务代码是

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

它在我的 app.component.ts 中使用如下:

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

如有任何帮助,我们将不胜感激。

编辑 1

服务:

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

组件:

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

原创

为此,您需要 Angular Services

它们是可以像共享状态一样运行的单例。您要做的是将您的数据存储在服务中,然后从您的两个组件调用服务并收听服务的 BehaviorSubject。

要在组件之间共享信息,您可以在将在不同组件中使用的服务中使用 behaviorSubject。

BehaviorSubject 的特点是它存储“当前”值,即最后一个值,需要与其他组件共享。

它的特殊性在于:

  • 需要一个初始值

    const subject = new MyBehaviorSubject('initialValue');

  • return主题最后值

  • 您可以使用 getValue() 方法检索最后一个值(不可观察)

    subject.getValue()

  • 可以订阅了:

    subject.subscribe(console.log);

  • 用 next()

    更新值

    subject.next('New value');

我举个例子: 在我的服务中:

 private isOpen = new BehaviorSubject<boolean>(false);

   public getNavBarOpen(): Observable<boolean> {
    return this.isOpen.asObservable();
  }

    setNavBarOpen(status: boolean): void {
     this.isOpen.next(status);
  }

在我的组件中:

如果我想更新值:

this.myService.setNavBarOpen(true);

如果我想获取值:

this.myService.getNavBarOpen().subscribe()

您可以在将保存此数据的服务中包含一个 属性,然后订阅它。我假设您将有一个定时间隔检查新响应 - 然后可以更新服务中 属性 的值。

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

这是一个描述如何使用纯 RxJS 完成的答案。另一种选择是使用 NgRx。

首先,你设置了两个科目。意图是所有组件都将订阅它们并在刷新时接收最新数据?

你应该使用 ReplaySubject 而不是 BehaviorSubject,因为你没有任何初始状态。由于数据作为一件事返回,我将使用一个主题。

首先,我将声明一个接口,以便于讨论数据类型。

地震-data.ts

export interface EarthquakeData {
  // TODO: create types for these
  geometries: any[]; 
  properties: any[];
}

在您的服务中,您可以通过自己的方法公开数据来分离检索和通知。

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

所以现在,所有想要接收数据的组件都将订阅一个方法:

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

并且您可以在任何地方刷新数据:

refreshData() {
  this.refreshing = true;
  this.earthquakeService.refreshEarthquakeData().subscribe(() => {
    this.refreshing = false;
  });
}

演示:https://stackblitz.com/edit/angular-uv7j33

解决此问题的简单方法是使用 BehaviorSubject。这方面的文档很全面,相信你能找到。

为了处理大型应用程序中的复杂状态,人们使用 Redux。对于Angular,有NgRx。

如果更新状态需要调用 API 作为副作用,请使用 ngrx/effects

https://ngrx.io/guide/effects

服务方法不需要return 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));
    })
});

和组件:

ngOnInit() {
    combineLatest(
        this.earthquakeService._earthquakePropertiesSource,
        this.earthquakeService._earthquakeGeometrySource
    ).subscribe(data => {
      this.properties = data[0];
      this.geometries = data[1];
      this.generateMapData();
});
}