【问题标题】:Angular 5 Component Not Updating When Observable ReturnsObservable 返回时 Angular 5 组件未更新
【发布时间】:2018-07-08 19:08:59
【问题描述】:

我正在使用 Angular 5 和 SignalR 构建应用程序。我创建了一个管理集线器的服务,当有事件从服务器进入时,它会将值放入 BehaviorSubject 私有变量中。有一个只读属性应该输出我可以从组件订阅的 observable。

在组件中,我已经订阅了服务的Observable,但是属性更新的时候还是没有更新。

我知道“测试”属性正在更新,因为我必须告诉我我收到了来自服务器的配置的警报。

有人能指出我可能遗漏了什么或不完全理解 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>

我也试过用这个直接订阅 observable。还是不行。

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

编辑:

我一直在做一些研究,并决定试试这个例子:Pushing Real-Time Data to an Angular Service using Web Sockets

执行此操作后,相同的行为仍然存在,没有触发任何更改检测。

经过更多研究并找到此页面:Angular 2 - View not updating after model changes

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

例子:

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

我还添加了一个警报,告诉我我正在运行的代码是否在 AngularZone 中。事实证明,我返回的可观察对象似乎不在 Angular 区域内。难怪我在更改属性时没有得到任何更改检测。

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

【问题讨论】:

  • 为什么测试用'Input()'修饰?只是设置初始值吗?仅在父子绑定上下文中检测到对 Input() 修饰成员的更改。另外,您确定这是 DisplayViewerConfigurationService 的唯一实例吗?
  • 啊,我不知道对 Input() 修饰成员的更改仅在父子绑定上下文中检测到。我关闭了 Input() 并没有改变行为。至于服务的只有一个实例。堆栈进入模块 -> 组件 -> 组件。我在构造函数中声明服务的唯一一次是在最后一个组件上。这是我从中订阅事件的唯一地方。如果有帮助,我会更新我的帖子以包含完整的堆栈。
  • 感谢您发布您的解决方案。我遇到了类似的问题——我不知道我的 Observable 在 Angular Zone 之外运行。我花了几个小时试图解决这个问题 - 如果没有你的解决方案,我不确定我是否会找到答案,所以谢谢!

标签: angular typescript signalr observable angular5


【解决方案1】:

所以,事实证明我正在使用 NgZone 之外的库。我能够通过使用 NgZone.isInAngularZone(); 来确定呼叫是否在区域内;并在订阅回调中放置警报。一旦我发现我不在该区域内,我发现我可以将 ChangeDetectorRef 注入组件并在订阅中放置手动更改检测调用。示例:this.changeDetector.detectChanges();

这只是解决根本问题的创可贴。经过一番研究,我发现了这个post。这指出您可以强制在区域内运行呼叫。所以我用这个替换了集线器代理回调。

this.hubProxy.on('receiveConfig', (e: any) => {
    this.ngZone.run(() => this.observer.next(e));
});

这迫使 observable 在 NgZone 中运行,允许默认更改检测树检测更改。

【讨论】:

    【解决方案2】:

    想补充一下我的新手经验...

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

    我的订阅之外有 2 个发出语句。 发射正在执行,我在更新发生之前刷新了父组件。

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

    事后看来很明显,但在我意识到自己的错误之前,我花了相当多的 console.log()-ing。

    希望对某人有所帮助。

    【讨论】:

      猜你喜欢
      • 2017-03-26
      • 2018-12-18
      • 2018-09-14
      • 1970-01-01
      • 2018-09-04
      • 2021-09-24
      • 2018-04-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多