当Observable返回时,Angular 5组件未更新

5
我正在使用Angular 5和SignalR构建一个应用程序。我创建了一个服务来管理hub,当服务器发送事件时,它会将值放入BehaviorSubject私有变量中。有一个只读属性应该输出可观察对象,我可以从组件中订阅。
在组件中,我已经订阅了服务的可观察对象,但是当属性更新时它仍然不会更新。
我知道“test”属性正在更新,因为我有警报告诉我我收到了来自服务器的配置。
请问有人能指出我可能遗漏的内容,或者我没有完全理解Angular如何处理更改检测,或者我可能破坏了什么吗?
服务:
import { Injectable, Input, Output, EventEmitter, NgZone } from '@angular/core';
import { Http, Response } from '@angular/http';
import 'rxjs/add/operator/map';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import { APIModels } from './../../shared/APIModels';
import {Observable} from "rxjs/Observable";

declare var $;
@Injectable()
export class DisplayViewerConfigurationService {
    private _displayConfiguration: BehaviorSubject<APIModels.ClientConfigurationModel>;
    public readonly displayConfiguraiton: Observable<APIModels.ClientConfigurationModel>;
    public hubProxy;
    @Input() displayID;

    constructor(public http: Http) {
        var test = new APIModels.ClientConfigurationModel();
        test.Display = new APIModels.DisplayConfig();
        test.Display.DisplayID = "testID";
        this._displayConfiguration = new BehaviorSubject(test);
        this.displayConfiguraiton = this._displayConfiguration.asObservable();
    }

    public startConnection(): void {
        var hubConnection = $.hubConnection("http://localhost:49890", {useDefaultCredentials: true});
        this.hubProxy = hubConnection.createHubProxy('testHub');
        hubConnection.qs = "displayid=" + this.displayID;
        this.hubProxy.on('receiveConfig', (e: any) => {
            this._displayConfiguration.next(e);
            alert("now" + JSON.stringify(e));
        });
        
        hubConnection.start();
    }
}

组件 TS:

import { Component, OnInit, Output, Input, ChangeDetectorRef } from '@angular/core';
import { DisplayViewerConfigurationService } from './../../services/displayviewerconfiguration-service/displayviewerconfiguration-service';
import 'rxjs/symbol/observable';
import { Observable } from 'rxjs/observable';
import { APIModels } from './../../shared/APIModels';
import { Helpers } from './../../shared/Helpers';

@Component({
  selector: 'app-displayiframe',
  templateUrl: './display-iframe.component.html',
  
})

export class DisplayScreenComponent implements OnInit {
  
    constructor(public viewer: DisplayViewerConfigurationService) {
        this.viewer.displayID = "278307b8-da34-4569-8b93-d5212b9e0e0d";
        this.viewer.startConnection();
        this.ObjCopy = new Helpers.ObjectCopyHelpers();
      
    }

    ngOnInit() {
        this.viewer.displayConfiguraiton.subscribe(data => {this.test = data; alert(JSON.stringify(this.test);});
    }

    @Input() test: any;
    public stackHeight: string;
    public ObjCopy: Helpers.ObjectCopyHelpers;
   
}

组件模板:

<div>
   {{test.Display.DisplayID}}
</div>

我也尝试过直接订阅这个可观察对象,但仍然没有成功。

<div>
   {{viewer.displayConfiguraiton.Display.DisplayID | async}}
</div>

编辑:

包括模块和父组件,以帮助提供进一步的澄清。

模块:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DisplayViewerConfigurationService } from './../services/displayviewerconfiguration-service/displayviewerconfiguration-service';
import { DisplayViewerComponent } from './display-viewer.component';
import { DisplayScreenComponent } from './display-iframe/display-iframe.component';
import { HttpModule } from '@angular/http';
import { APIModels } from "./../shared/APIModels";
import { Helpers } from "./../shared/Helpers";

@NgModule({
  imports: [
    CommonModule
    HttpModule,
  ],
  declarations: [DisplayViewerComponent, DisplayScreenComponent],
  
  entryComponents: [DisplayViewerComponent, DisplayScreenComponent],
  
  providers: [DisplayViewerConfigurationService],
  exports: [DisplayViewerComponent],
})
export class DisplayViewerModule { }

父组件:

import { Component, OnInit } from '@angular/core';
import { NouisliderModule, NouisliderComponent } from 'ng2-nouislider/src/nouislider';
import { DisplayViewerConfigurationService } from './../services/displayviewerconfiguration-service/displayviewerconfiguration-service';
import { DisplayScreenComponent } from './display-iframe/display-iframe.component';
import 'rxjs/symbol/observable';
import { Observable } from 'rxjs/observable';

@Component({
  selector: 'app-displayviewer',
  templateUrl: './display-viewer.component.html'
  
})

export class DisplayViewerComponent implements OnInit {
  constructor() {
}

  ngOnInit() {

  }

}

父组件模板:

<app-displayiframe></app-displayiframe>

编辑:

我进行了一些研究,并决定尝试这个示例:使用 Web Sockets 推送实时数据到 Angular 服务

即使在这样做后,相同的行为仍然存在,没有触发任何变化检测。

经过更多的研究并找到这个页面:Angular 2 - 模型更改后视图不更新

我决定在更新组件属性后尝试运行 ChangeDetectorRef.detectChanges();。这样做实际上解决了我的问题,并导致组件按预期更新。

示例:

this.viewer.startConnection()
    .subscribe(config => {
        this.test.push(config);
        alert(JSON.stringify(this.test));
        this.changeDetector.detectChanges();
        alert(NgZone.isInAngularZone());
    });

我还添加了一个提示,告诉我运行的代码是否在AngularZone内。结果显示,我返回的observable似乎不在Angular Zone中。难怪当更改属性时我没有得到任何变化检测。

为什么用这种方式创建observable会导致订阅在AngularZone之外?有没有办法将其修复为在该区域范围内,而不必依赖手动强制应用程序检测更改?


为什么test被装饰为'Input()'?这只是为了设置初始值吗?只有在父子绑定上下文中才会检测到对Input()装饰成员的更改。此外,您确定这是DisplayViewerConfigurationService的唯一实例吗? - Abraham Al-Dabbagh
啊,我不知道Input()修饰的成员变量只有在父子绑定上下文中才能检测到更改。我取消了Input(),但它并没有改变行为。至于Service只有一个实例。堆栈是模块->组件->组件。我只在最后一个组件中声明服务的构造函数。那是我订阅它的事件的唯一地方。如果需要,我会更新我的帖子以包含完整的堆栈信息。 - Pyrodius
感谢您发布解决方案。我遇到了类似的问题——我不知道我的Observable在Angular区域之外运行。我花了几个小时来尝试解决这个问题,如果没有您的解决方案,我不确定我能否找到答案,所以非常感谢您! - Clay
2个回答

6
所以,事实证明我正在使用一个不在NgZone中的库。我能够通过使用NgZone.isInAngularZone();并在订阅回调中放置警报来确定调用是否在该区域内。一旦我发现自己离开了该区域,我就想出了可以将ChangeDetectorRef注入到组件中,并在订阅中放置手动更改检测调用的方法。例如:this.changeDetector.detectChanges();
这只是解决根本问题的权宜之计。经过更多的研究,我发现了这个post。这指出你可以强制调用在该区域内运行。所以我用这个替换了中心代理回调。
this.hubProxy.on('receiveConfig', (e: any) => {
    this.ngZone.run(() => this.observer.next(e));
});

这将强制可观察对象在NgZone内运行,从而使默认变更检测树能够检测到更改。

0

想分享一下我的新手经验...

obs.subscribe(
        (data) => {
            console.log('this was sent to the DB : ' + data);
            this.myItem = data;
            this.itemOUT.emit(this.myItem);
            this.itemCLOSE.emit(true);

        });

我把两个发射语句放在了我的订阅之外。 在更新发生之前,这些发射语句被执行,并且我正在刷新父组件。

对我来说,这个时机有点违反直觉,但它确实发生了(一直如此)。这只是证明了在异步操作中,你不应该做任何假设。

事后看来很明显,但在我意识到错误之前,我花了相当多的时间使用 console.log() 进行调试。

希望对某人有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接