【问题标题】:How to use BehaviourSubjects to share data from API call between components in Angular?如何使用 BehaviourSubjects 在 Angular 中的组件之间共享来自 API 调用的数据?
【发布时间】:2020-03-05 09:43:01
【问题描述】:

我目前正在构建一个 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);
    }

任何帮助将不胜感激。

【问题讨论】:

    标签: angular rxjs observable angular-httpclient behaviorsubject


    【解决方案1】:

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

    首先,您设置了两个主题。目的是所有组件都会订阅它们并在刷新时接收最新数据?

    您应该使用ReplaySubject 而不是BehaviorSubject,因为您没有任何初始状态。而且由于数据是一回事,所以我会使用一个主题。

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

    地震数据.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

    【讨论】:

    • 嗨,库尔特。感谢您的详细回复。我尝试实施您提供的解决方案,但出现了一些错误。 ```找不到名称几何体找不到名称属性找不到名称响应```
    • 抱歉,我打错了几个字。我已经更正了它们并添加了一个演示链接
    • 没问题,非常感谢!所以现在我可以在我的所有组件中调用相同的 getEarthquakeData() 和 refreshEarthquakeData() 了吗?
    • 是的 - 任何组件都可以订阅和刷新。刷新不需要取消订阅,因为 http 调用是自完成的。您的 get 需要取消订阅,因为他们订阅了您自己创建的主题。
    • 我需要在组件的 ngOnDestroy() 中取消订阅吗?另外,我如何能够每 x 分钟进行一次 api 调用?
    【解决方案2】:

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

    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】:

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

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

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

      https://ngrx.io/guide/effects

      【讨论】:

        【解决方案4】:

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

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

        它的特殊性是:

        • 需要一个初始值

          const subject = new MyBehaviorSubject('initialValue');

        • 返回主题的最后一个值

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

          subject.getValue()

        • 您可以订阅它

          subject.subscribe(console.log);

        • 使用 next() 更新值

          subject.next('新值');

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

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

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            【解决方案6】:

            编辑 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。

            【讨论】:

            • 感谢您的评论。我目前正在使用服务。第一个代码 sn-p 是我的服务,但我不确定如何实现 BehaviourSubject 功能以便我可以跟踪状态
            猜你喜欢
            • 2018-10-12
            • 2019-08-04
            • 1970-01-01
            • 2019-09-20
            • 2019-04-28
            • 2017-11-06
            • 2021-05-03
            • 2023-03-10
            相关资源
            最近更新 更多