Angular 订阅可观察但运行时未准备好的数据

Angular 订阅可观察但运行时未准备好的数据,angular,observable,Angular,Observable,我无法将服务中的可观测数据与使用数据的组件同步。数据服务在创建时调用api服务,以生成从服务器获取的设备列表。由于设备列表是在数据服务中构建的,因此这似乎工作正常 import { Injectable } from '@angular/core'; import { ApiService } from './api.service'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export

我无法将服务中的可观测数据与使用数据的组件同步。数据服务在创建时调用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;
  }
}
从'@angular/core'导入{Injectable};
从“/api.service”导入{ApiService};
从“rxjs”导入{Observable};
@注射的({
providedIn:'根'
})
导出类设备管理服务{
DeviceInfo=null;
deviceInfo=null;
设备=[];
构造函数(私有apiService:apiService){
this.apiService.getDeviceStatus().subscribe((resp)=>{
this.devicesInfo=resp;
console.log('DeviceManager:deviceInfo:',this.deviceInfo);
此.buildDeviceTable(resp);
console.log('devices[]=',this.devices);
}); 
}
buildDeviceTable(设备信息){
DeviceInfo.record.forEach(设备=>{
console.log('record.devid=',device.devid');
if(this.devices.indexOf(device.devid)>-1){
//log('element',device.devid',已经在设备数组中');
}
否则{
//this.devices.push({device:device.devid});
这个.devices.push(device.devid);
//log('added',device.devid',to devices array');
}
})
}
getDevices():可观察{
让数据=新的可观察对象(观察者=>{
observer.next(本设备);
});
返回数据;
}
}
在我的组件中,我希望在mat表中显示该设备列表。我将设备列表设置为数据服务中的可观察设备,组件订阅了该列表。但是,当subscribe函数运行时,数据不会在可观察对象中返回-数组为空,并且forEach循环不会运行以将设备ID从可观察对象复制到用作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); 
  }

}
从“/../../services/device manager.service”导入{devicemanager服务};
从“@angular/core”导入{Component,OnInit};
从“@angular/material”导入{MatTableModule,MatTableDataSource};
从“rxjs”导入{Observable};
@组成部分({
选择器:“应用程序设备”,
templateUrl:'./devices.component.html',
样式URL:['./devices.component.css']
})
导出类DeviceComponent实现OnInit{
displayedColumns:string[]=['deviceID'];
//设备=空;
设备=[“test1”、“test2”];
deviceData=null;
构造函数(专用设备管理器:设备管理器服务){
this.deviceManager.getDevices().subscribe(devTable=>{
log('devTable:length',devTable.length);
devTable.forEach(设备=>{
log('forEach:device:',device);
});
console.log('devices:',this.devices);
//这个。设备=可开发的;
});   
}
恩戈尼尼特(){
this.deviceData=新MatTableDataSource(this.devices);
console.log('DeviceComponent:',this.deviceData);
}
}
设备保持其默认的初始化值,并且永远不会被分配来自设备管理器的值。但是设备管理器已经建立了正确的设备列表,由于某些原因,它无法通过subscribe函数访问组件

如何确保在subscribe函数中提供数据


谢谢……

我不是100%肯定,但我相信它不起作用的原因是,在将数据传递给它之前,需要订阅你的observable。在代码中,首先使用observer.next(this.devices)传递数据,然后订阅。订阅时,数据已发送,事件已完成

有两种方法可以解决这个问题,一种是使用行为主体

  • 在服务的顶部添加属性
    devices$=new behavior subject([])

  • 在方法“buildDeviceTable()”的末尾添加一行
    this.devices$.next(this.devices)

  • 在组件中,将
    this.deviceManager.getDevices().subscribe(
    替换为
    this.deviceManager.devices$.subscribe(


  • 您应该使用
    ReplaySubject
    BehaviorSubject
    。如果您只希望它在发出第一个值时发出一个值,则将使用第一个。如果您希望它具有默认值(例如,空数组),则使用后一个

    您甚至可以将服务更新为完全使用流,这将始终保持数据的最新状态,并大大减少
    .subscribe
    的使用,这是导致内存泄漏(未关闭的订阅)的一大原因:

    由于您现在使用的是streams,因此
    MatTableDataSource
    的使用变得过时,因为您只需将observable as数据源传递给mat表:

    export class DevicesComponent {
      displayedColumns: string[] = ['deviceID'];
    
      readonly devices$ = this.deviceManager.devices$;
    
      constructor(private deviceManager: DeviceManagerService) {}
    }
    
    如您所见,我没有使用
    行为子对象
    ReplaySubject
    ,但我使用了
    shareReplay()
    操作符。它基本上在
    ReplaySubject
    中转换一个可观察对象,并在订阅者之间共享订阅

    但是请注意,使用
    shareReplay(1)
    时应谨慎。如果您有一个未完成的可观察对象,即使组件被销毁,订阅也不会结束。您可以添加一个
    takeUntil(//销毁可观察对象)
    ,或者将其更改为
    shareReplay({refCount:true,bufferSize:1})
    。一旦订阅计数达到0,后者将重新启动原始的
    可观察的
    ,并在之后再次订阅。不过,好的是,事情确实得到了清理。因此..这只是一个脚注:)

    我认为当您订阅组件中的
    this.deviceManager.getDevices()
    时,异步请求尚未完成,您服务中的
    this.devices
    仍然是空的。带有
    行为主题的解决方案
    
    export class DevicesComponent {
      displayedColumns: string[] = ['deviceID'];
    
      readonly devices$ = this.deviceManager.devices$;
    
      constructor(private deviceManager: DeviceManagerService) {}
    }