Как использовать BehaviourSubjects для обмена данными из вызова API между компонентами в Angular? - PullRequest
0 голосов
/ 05 марта 2020

В настоящее время я создаю приложение 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);
    }

Любой Помощь будет принята с благодарностью.

Ответы [ 6 ]

1 голос
/ 05 марта 2020

Это ответ, описывающий, как это можно сделать, используя чистый Rx JS. Другой альтернативой является использование NgRx.

Во-первых, вы настроили два предмета. Предполагается, что все компоненты подпишутся на них и получат самые свежие данные после обновления?

Вы должны использовать ReplaySubject вместо BehaviorSubject, поскольку у вас нет начального состояния. И поскольку данные возвращаются как одна вещь, я бы использовал одну тему.

Во-первых, я собираюсь объявить интерфейс, чтобы было легче говорить о типах данных.

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

И вы можете sh обновить данные, где хотите:

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

ДЕМО: https://stackblitz.com/edit/angular-uv7j33

0 голосов
/ 05 марта 2020

Служебному методу не нужно возвращать 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();
});
}
0 голосов
/ 05 марта 2020

Простой способ go об этом - использовать BehaviorSubject. Документация по этому вопросу является исчерпывающей, я уверен, что вы можете ее найти.

Для обработки сложных состояний в больших приложениях люди используют Redux. Для Angular есть NgRx.

Если для обновления состояния требуется вызвать API в качестве побочного эффекта, используйте ngrx/effects

https://ngrx.io/guide/effects

0 голосов
/ 05 марта 2020

Вы можете включить в службу свойство, которое будет хранить эти данные, и подписаться на него. Я предполагаю, что у вас будет временной интервал проверки новых ответов, который затем может просто обновить значение свойства в сервисе.

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)
                        });
                    });
    }
}
0 голосов
/ 05 марта 2020

Чтобы обмениваться информацией между компонентами, вы можете использовать поведениеSubject в службе, которая будет использоваться в ваших различных компонентах.

BehaviorSubject имеет характеристику c, в которой хранится «текущее» значение, последнее значение, которое необходимо использовать совместно с другими компонентами.

Его особенности:

  • необходимо начальное значение

    const subject = new MyBehaviorSubject ('initialValue');

  • вернуть последнее значение субъекта

  • Вы можете получить последнее значение с помощью метода getValue () (ненаблюдаемый)

    subject.getValue ()

  • вы можете подписаться на него :

    subject.subscribe (console.log);

  • обновить значение следующим ()

    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()
0 голосов
/ 05 марта 2020

Редактировать 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 Услуги

Это синглтоны, которые могут действовать как общее состояние. Что вы хотите сделать, так это сохранить ваши данные внутри службы, а затем вызвать службу из обоих ваших компонентов и прослушать BehaviorSubject службы.

...