I am building an application using Angular 5 and SignalR. I created a service which manages the hub, and when ever an event comes in from the Server it puts the value in the BehaviorSubject private variable. There is a read only property that should put out the observable which I can subscribe to from the component.
In the component, I have subscribed to the Observable of the service, but it still does not update when the property updates.
I know the "test" property is being updated because of the alerts that I have to tell me I received the config from the server.
Would someone be able to point out what I may be missing or do not fully understand about how Angular handles change detection or how I may have broken that?
Service:
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();
}
}
Component 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;
}
Component Template:
<div>
{{test.Display.DisplayID}}
</div>
I have also tried subscribing directly to the observable with this. Still didn't work.
<div>
{{viewer.displayConfiguraiton.Display.DisplayID | async}}
</div>
EDIT:
Including the Module, and parent component for this stack to help provide further clarification.
MODULE:
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 { }
Parent Component:
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() {
}
}
Parent Component Template:
<app-displayiframe></app-displayiframe>
EDIT:
I have been doing a bit of research and decided to give this example a try: Pushing Real-Time Data to an Angular Service using Web Sockets.
The same behavior persisted after doing this, with no change detection being triggered.
After doing a bit more research and finding this page: Angular 2 - View not updating after model changes.
I decided to attempted to run ChangeDetectorRef.detectChanges();
after I update the property of the component. Doing this actually fixed my problem and caused the component to update as I expected it to.
Example:
this.viewer.startConnection()
.subscribe(config => {
this.test.push(config);
alert(JSON.stringify(this.test));
this.changeDetector.detectChanges();
alert(NgZone.isInAngularZone());
});
I also added an alert to tell me if the code I was running was within the AngularZone. As it turns out, the observable I am returning doesn't seem to be within the Angular Zone. No wonder I was not getting any change detection when changing the property.
Why would creating the the observable this way cause the subscription to be outside the AngularZone? Is there a way I can fix this to be within the bounds of the zone and not have to rely on manually forcing the application to detect changes?