подписаться на наблюдаемые, но данные не готовы при запуске - PullRequest
0 голосов
/ 02 февраля 2020

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

import { Injectable } from '@angular/core';
import { ApiService } from './api.service';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class DeviceManagerService {
  devicesInfo = null;
  deviceInfo = null;
  devices = [];

  constructor(private apiService: ApiService ) { 
    this.apiService.getDeviceStatus().subscribe((resp) => {
      this.devicesInfo = resp;
      console.log('DeviceManager: deviceInfo: ',this.devicesInfo);
      this.buildDeviceTable(resp);
      console.log('devices[]=', this.devices);
    }); 
  }

  buildDeviceTable(devicesInfo) {

    devicesInfo.record.forEach( device => {
      console.log('record.devid= ', device.devid);
      if ( this.devices.indexOf(device.devid) > -1) {
        //console.log('element ', device.devid, ' already in devices array');
      }
      else {
        //this.devices.push({ device: device.devid });
        this.devices.push(device.devid);
        //console.log('added ', device.devid, ' to devices array');
      }

    })
  }

  getDevices(): Observable<string[]> {
    let data = new Observable<string[]>(observer => {
      observer.next(this.devices);
    });
    return data;
  }
}

В моем компоненте я хочу отобразить этот список устройств в таблице соответствия. У меня есть список устройств, настроенных как наблюдаемые в службе данных, и компонент подписывается на это. Но когда функция подписки запущена, данные не возвращаются в наблюдаемую - массив пуст, и forEach l oop не запускается для копирования идентификаторов устройства из наблюдаемой в локальный массив, который используется в качестве источника данных. Для таблицы mat.

import { DeviceManagerService } from './../../services/device-manager.service';
import { Component, OnInit } from '@angular/core';
import { MatTableModule, MatTableDataSource } from '@angular/material';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-devices',
  templateUrl: './devices.component.html',
  styleUrls: ['./devices.component.css']
})
export class DevicesComponent implements OnInit {
  displayedColumns: string[] = ['deviceID'];
  //devices = null;
  devices=["test1","test2"];
  deviceData = null;

  constructor(private deviceManager: DeviceManagerService) {
    this.deviceManager.getDevices().subscribe( devTable => {
      console.log('devTable: length', devTable.length);
      devTable.forEach( device => {
        console.log('forEach: device: ', device);
      });
      console.log('devices: ', this.devices);
      //this.devices = devTable;
    });   
   }

  ngOnInit() {
    this.deviceData = new MatTableDataSource<any>(this.devices);
    console.log('devicessComponent: ', this.deviceData); 
  }

}

устройства остаются с начальным значением по умолчанию и никогда не получают назначенные значения, которые поступают из диспетчера устройств. Но диспетчер устройств создал правильный список устройств, по какой-то причине он не попадает в компонент через функцию подписки.

Как мне убедиться, что данные предоставлены в функции подписки?

Спасибо ....

Ответы [ 2 ]

0 голосов
/ 02 февраля 2020

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

Вы можете даже обновить свой сервис, чтобы он был полностью с потоками, что всегда будет поддерживать ваши данные в актуальном состоянии и значительно сокращает использование .subscribe, которое является большой причиной утечек памяти (незакрытые подписки):

@Injectable({
  providedIn: 'root'
})
export class DeviceManagerService {
  readonly devicesInfo$ = this.apiService.getDeviceStatus().pipe(
    shareReplay(1)
  );

  readonly devices$ = this.devicesInfo$.pipe(
    map((devicesInfo) => devicesInfo.record
      .filter((dev, i, arr) => arr.findIndex(({ devid }) => devid === dev.devid) === i)
    ),
    shareReplay(1)
  );

  constructor(private apiService: ApiService ) {}
}

И поскольку вы используете потоки сейчас, использование MatTableDataSource становится устаревшим, поскольку вы можете просто передать наблюдаемый в качестве источника данных для таблицы соответствия:

export class DevicesComponent {
  displayedColumns: string[] = ['deviceID'];

  readonly devices$ = this.deviceManager.devices$;

  constructor(private deviceManager: DeviceManagerService) {}
}

Как видите, я не использую BehaviorSubject или ReplaySubject, но я использую оператор shareReplay(). Что в основном превращает наблюдаемое в ReplaySubject и распределяет подписки среди подписчиков.

Имейте в виду, однако, что использование shareReplay(1) следует делать с осторожностью. Если у вас есть наблюдаемое, которое не завершается, подписка не заканчивается, даже если компонент уничтожается. Вы можете либо добавить takeUntil(//destroy observable), либо изменить его на shareReplay({ refCount: true, bufferSize: 1 }). Последний перезапустит исходный Observable, как только количество подписок достигнет 0, и после этого снова будет подписано. Хорошая вещь - то, что вещи действительно убираются. Так что .. это просто сноска :)

0 голосов
/ 02 февраля 2020

Я не уверен на 100%, но я полагаю, что причина, по которой он не работает, заключается в том, что на ваше наблюдаемое необходимо подписаться, прежде чем вы передадите ему данные. В своем коде вы сначала передаете данные с помощью наблюдателя.next (this.devices), а затем подписываетесь. К тому времени, когда вы подписываетесь, данные уже отправлены, и событие уже завершено.

Есть несколько способов, которые могут быть решены, один с BehaviorSubject.

  1. В верхней части вашего сервиса вы добавляете свойство devices$ = new BehaviourSubject<string[]>([]);.

  2. В конце вашего метода buildDeviceTable () вы добавляете строку this.devices$.next(this.devices);.

  3. В вашем компоненте вы заменяете this.deviceManager.getDevices().subscribe( на this.deviceManager.devices$.subscribe(

...