_From @jacobkuriala on November 28, 2017 17:10_
I seem to be having a similar issue to #4311 . Not sure if I'm doing it right or if there is a bug. I have got a service that creates a web socket and creates and returns an observable. My component subscribes to the observable and changes the component data members when new data arrives. The data members get updated in the component (as observed through console.log) but the labels are not updated on the UI (whose text attribute are linked through property binding). If I move to a different tab and move back the latest data gets shown. I also tried both the change detection strategies.
P.S: I am using the angular tabbed template from sidekick as the initial template.
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from "";
import {MYService} from "../../my.service.service";
import { Subscription } from 'rxjs/Subscription';
@component({
selector: "Home",
moduleId: module.id,
templateUrl: "./home.component.html",
changeDetection: ChangeDetectionStrategy.Default
})
export class HomeComponent implements OnInit,OnDestroy {
public prop1:number;
prop2:number;
prop3:number;
subscription: Subscription;
constructor(private myService: MYService ) { }
ngOnInit() {
this.createSocket();
}
createSocket(){
this.subscription = this.myService.startSubscription().subscribe(
(message)=>{
if(message.data){
let data = JSON.parse(message.data);
if(data.type === "ticker"){
// console.dir(data);
if(data.product_id === "a"){ this.prop1 = data.price; console.log(this.prop1); } // shows updated data in console
else if(data.product_id === "b"){ this.prop2 = data.price; console.log(this.prop2); } // shows updated data in console
else if(data.product_id === "c"){ this.prop3 = data.price; console.log(this.prop3);}
else{console.log(data);}
}
}
}
// on error and complete not implemented
);
}
unsubscribe(){
this.subscription.unsubscribe();
this.myService.stopSubscription();
}
ngOnDestroy(){
this.unsubscribe();
}
_Copied from original issue: NativeScript/NativeScript#5100_
Hi @jacobkuriala,
Thank you for contacting us,
Could you please share the code for your custom services and the package.json file from the project? This will allow us to investigate further this case on our side.
In the meantime, you could try adding NativeScritpFormsModules in the imports. For example:
app.module.ts
import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { AppRoutingModule } from "./app.routing";
import { AppComponent } from "./app.component";
import { ItemService } from "./item/item.service";
import { ItemsComponent } from "./item/items.component";
import { ItemDetailComponent } from "./item/item-detail.component";
// Uncomment and add to NgModule imports if you need to use two-way binding
import { NativeScriptFormsModule } from "nativescript-angular/forms";
// Uncomment and add to NgModule imports if you need to use the HTTP wrapper
// import { NativeScriptHttpModule } from "nativescript-angular/http";
@NgModule({
bootstrap: [
AppComponent
],
imports: [
NativeScriptModule,
AppRoutingModule,
NativeScriptFormsModule
],
declarations: [
AppComponent,
ItemsComponent,
ItemDetailComponent
],
providers: [
ItemService
],
schemas: [
NO_ERRORS_SCHEMA
]
})
/*
Pass your application module to the bootstrapModule function located in main.ts to start your app
*/
export class AppModule { }
This module is needed in case two-way binding.
Thanks tsonevn,
I tried adding the nativescriptformsmodule but didnt work (I am only using one way binding)
Here is the service code (some var names have been changed): (BTW I am able to see the data changes in the component when console.logging the component members)
import { Subject } from 'rxjs/Subject';
import { Observable} from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import { Injectable } from '@angular/core';
require('nativescript-websockets');
@Injectable()
export class WebsocketService {
private socketObservable: Observable<MessageEvent>;
private socket:WebSocket;
constructor() {
}
public startSubscription(): Observable<MessageEvent>{
// realtime
let url = 'wss://url';
if(!this.socketObservable) {
this.socketObservable = this.create(url);
}
return this.socketObservable;
}
private create(url): Observable<MessageEvent> {
if(!this.socket || this.socket.readyState !== WebSocket.OPEN){
// create web socket and send initial request
this.socket = new WebSocket(url);
console.log("attempting to create web socket")
let subrequest = {
"type": "subscribe",
"product_ids": [
// subscription reqeust
]
};
this.socket.onopen = () => {
console.log(subrequest);
this.socket.send(JSON.stringify(subrequest));
console.log('web socket stream opened');
};
}
//bind web socket as observable
let observable = Observable.create(
(obs: Observer<MessageEvent>) => {
this.socket.onmessage = obs.next.bind(obs);
this.socket.onerror = obs.error.bind(obs);
this.socket.onclose = obs.complete.bind(obs);
return this.socket.close.bind(this.socket);
}
);
return observable;
}
public stopSubscription(){
if(this.socket){
let unsubrequest = {
//unsubscribe request
};
this.socket.send(JSON.stringify(unsubrequest));
}
}
}
Here is the package.json file
{
"description": "NativeScript Application",
"license": "SEE LICENSE IN <your-license-filename>",
"readme": "NativeScript Application",
"repository": "<fill-your-repository-here>",
"nativescript": {
"id": "org.nativescript.<appid>",
"tns-android": {
"version": "3.3.1"
}
},
"scripts": {
"lint": "tslint \"app/**/*.ts\""
},
"dependencies": {
"@angular/animations": "4.4.6",
"@angular/common": "4.4.6",
"@angular/compiler": "4.4.6",
"@angular/core": "4.4.6",
"@angular/forms": "4.4.6",
"@angular/http": "4.4.6",
"@angular/platform-browser": "4.4.6",
"@angular/router": "4.4.6",
"nativescript-angular": "4.4.1",
"nativescript-theme-core": "1.0.4",
"nativescript-websockets": "^1.3.4",
"reflect-metadata": "0.1.10",
"rxjs": "5.5.2",
"tns-core-modules": "3.3.0",
"zone.js": "0.8.18"
},
"devDependencies": {
"babel-traverse": "6.4.5",
"babel-types": "6.4.5",
"babylon": "6.4.5",
"codelyzer": "3.2.2",
"lazy": "1.0.11",
"nativescript-dev-sass": "1.3.2",
"nativescript-dev-typescript": "0.5.1",
"node-sass": "4.5.3",
"tslint": "5.8.0",
"typescript": "2.4.2"
}
}
HI @jacobkuriala,
Thank you for providing the code for the service.
I reviewed it and found that the issue is related to the way the message event listener is defined. the callback method should be rapped in NgZone. For example:
let observable = Observable.create(
(obs: Observer<MessageEvent>) => {
this.socket.onmessage = (message) => {
this.zone.run(() => obs.next(message))
};
this.socket.onerror = obs.error.bind(obs);
this.socket.onclose = obs.complete.bind(obs);
return this.socket.close.bind(this.socket);
}
);
This will allow triggering a notification when the value is changed and the UI should be refreshed.
I am also attaching the full source:
import { Subject } from 'rxjs/Subject';
import { Observable} from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import { Injectable, NgZone } from '@angular/core';
require('nativescript-websockets');
@Injectable()
export class TmpService extends Observable<any>{
private socketObservable: Observable<MessageEvent>;
private socket:WebSocket;
constructor(private zone:NgZone) {
super()
}
public startSubscription(): Observable<MessageEvent>{
// realtime
let url = 'ws://demos.kaazing.com/echo';
if(!this.socketObservable) {
this.socketObservable = this.create(url);
}
return this.socketObservable;
}
private create(url): Observable<MessageEvent> {
if(!this.socket || this.socket.readyState !== WebSocket.OPEN){
// create web socket and send initial request
this.socket = new WebSocket(url);
console.log("attempting to create web socket")
let subrequest = {
"type": "subscribe",
"product_ids": [
{"price":2}
]
};
this.socket.onopen = () => {
console.dir(subrequest);
this.socket.send(JSON.stringify(subrequest));
console.log('web socket stream opened');
};
}
//bind web socket as observable
let observable = Observable.create(
(obs: Observer<MessageEvent>) => {
this.socket.onmessage = (message) => {
this.zone.run(() => obs.next(message))
};
this.socket.onerror = obs.error.bind(obs);
this.socket.onclose = obs.complete.bind(obs);
return this.socket.close.bind(this.socket);
}
);
return observable;
}
public stopSubscription(){
if(this.socket){
let unsubrequest = {
//unsubscribe request
};
this.socket.send(JSON.stringify(unsubrequest));
}
}
}
Thanks for your response tsonevn. But the fix didnt resolve the UI update issue for me.
After implementing the changes the data changes are no longer propagated to the component either (as observed by console.logging the property changes in the component)
Do I need to make any changes in the component?
P.S: one thing I forgot to mention in the original posting is that the component and service code I used initially, works on a normal angular web application using javascript web sockets. The 3 things I see different in this implementation are:
1) Use of nativescript-websockets
2) Use of nativescript
3) Use of nativescript sidekick template.
Maybe this might give you some hint of what the problem maybe? Let me know
Thanks!
I have this problem also and It works as @jacobkuriala says, if I console log it is there but UI doesn't refresh. Weird thing is that I have other project when I fixed this problem wrapping the callback method inside ngZone but for this situation nothing happens.
(I checked my ngModule for NativeScriptFormsModule)
```
listenPickupClient() {
this.busService.subscribe("client-action-pickup", (client) => {
console.log("Received client => ", JSON.stringify(client));
if (client) {
this.setClient(client);
}
})
}
setClient(client: Client) {
this.ngZone.run(() => {
this.client = client;
console.log("Received client => OK => ", JSON.stringify(this.client));
});
}
```
Thank you, ChangeDetectorRef.markForCheck() was not helping and wrapping into NgZone.run() has helped.
Perhaps ApplicationRef.tick() will also work, but I haven't tried yet and it seems using NgZone is the most proper way.
I was also experiencing UI not reflecting data changes. I had UI components bound to model data members, which were updated via observables. I wrapped my updates in this.zone.run(...) which resolved the issue.
I fixed this issue by using the patched version of rxjs: see https://github.com/angular/zone.js/blob/master/NON-STANDARD-APIS.md#usage
main.tns.ts
// this import should be first in order to load some required settings (like globals and reflect-metadata)
import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import "zone.js/dist/zone-patch-rxjs";
(...)
Most helpful comment
HI @jacobkuriala,
Thank you for providing the code for the service.
I reviewed it and found that the issue is related to the way the message event listener is defined. the callback method should be rapped in NgZone. For example:
This will allow triggering a notification when the value is changed and the UI should be refreshed.
I am also attaching the full source: