1

I'm trying to update the totalRequests, payloadSize, payloadSizeType properties every time I receive a message from the WebSocket. This works successfully, and I can confirm by the console.log's as they update correctly every log according to what's being sent from the server.

The problem is that my template is not updating to reflect these changes for some reason.

I can confirm that there is no other underlying error because when I make a test function and update those values on click, they update and the changes are indeed reflected in the template, so I've concluded that the error must be coming from the way I am passing the WS callbacks in...unless there's something else I'm unaware of.

import { Component, OnInit } from '@angular/core';

import { AttackService } from './attack.service';
import { WebSocketService } from '../../../lib/service/websocket/websocket.service';
import { WebSocketConfig } from '../../../lib/service/websocket/websocket.config';

@Component({
  selector: 'attack',
  templateUrl: './attack.component.html',
  styleUrls: ['./attack.component.css'],
  providers: [AttackService, WebSocketService]
})
export class AttackComponent implements OnInit {
  private model: Object = {
    url: "http://localhost",
    kb_per_request: 5,
    duration: 5
  };

  private hasReceivedMessage: boolean = false;
  private totalRequests: number = 0;
  private payloadSize: number = 0;
  private payloadSizeType: string = 'KB';

  constructor(private attackService: AttackService, private socket: WebSocketService) {}

  ngOnInit(): void {
    this.socket.create(new WebSocketConfig(this.sOnOpen, this.sOnClose, this.sOnMessage, this.sOnError));
  }

  sOnOpen(): void {
    console.log('WebSocket connection successfully established.');
  }

  sOnClose(code: number): void {
    console.log(`WebSocket connection closed (${code}).`);
  }

  sOnMessage(data: any): void {
    this.hasReceivedMessage = true;

    this.totalRequests = data.total_requests;
    this.payloadSize = data.payload_size;
    this.payloadSizeType = data.payload_size_type;

    console.log('====================================');
    console.log('Total requests: ', this.totalRequests);
    console.log('Payload size: ', this.payloadSize);
    console.log('Payload type: ', this.payloadSizeType);
  }

  sOnError(data: any): void {
    console.log('WebSocket error occurred: ', data);
  }

  submit(): void {
    this.attackService.attack(this.model).subscribe(
        res => {
          let data = res.json();

          if (data.success) {
            console.log(data.message);
          }
        },
        err => {
          console.log('Error:', err);
        }
      );
  }
}

I thought I was passing in the methods with the wrong this context, but even when binding the correct context, the template still does not update to reflect the changes.

constructor(private attackService: AttackService, private socket: WebSocketService) {
    this.sOnOpen = this.sOnOpen.bind(this);
    this.sOnClose = this.sOnClose.bind(this);
    this.sOnMessage = this.sOnMessage.bind(this);
    this.sOnError = this.sOnError.bind(this);
  }

Anyone know a solution to this problem?

UPDATE:

WebSocketService:

import { Injectable } from '@angular/core';
import { WebSocketConfig } from './websocket.config';

@Injectable()
export class WebSocketService {
  private socket = null;
  private uri = "ws://localhost:80/ws";

  create(config: WebSocketConfig): void {
    window.onload = () => {
      this.socket = new WebSocket(this.uri);
      this.socket.onopen = config.onOpen;
      this.socket.onclose = config.onClose;
      this.socket.onmessage = res => config.onMessage(JSON.parse(res.data));
      this.socket.onerror = config.onError;
    };
  }

  send(data: any): void {
    this.socket.send(data);
  }
}
1
  • Have you tried triggering change detection manually? Commented Mar 28, 2017 at 5:32

4 Answers 4

1

Indeed, it does look like a context issue. Have you tried the following:

this.socket.create(new WebSocketConfig(this.sOnOpen.bind(this), this.sOnClose.bind(this), this.sOnMessage.bind(this), this.sOnError.bind(this)));

EDIT: I'll be honest, I'm shooting the dark on this one. But maybe change this method to bind on config:

create(config: WebSocketConfig): void {
    window.onload = () => {
      this.socket = new WebSocket(this.uri);
      this.socket.onopen = config.onOpen.bind(config);
      this.socket.onclose = config.onClose.bind(config);
      this.socket.onmessage = res => config.onMessage.bind(config));
      this.socket.onerror = config.onError.bind(config);
    };
  }
Sign up to request clarification or add additional context in comments.

2 Comments

Yeah I tried that before I put them into the constructor; neither work. I'm new to Angular2, so I have no clue now...but I figured this was just a JavaScript logic error. Apparently not.
Updated my post.
1

Try updating your sOnMessage method to wrap itself in a setTimeout.

sOnMessage(data: any): void {
  setTimeout(() => {
    this.hasReceivedMessage = true;

    this.totalRequests = data.total_requests;
    this.payloadSize = data.payload_size;
    this.payloadSizeType = data.payload_size_type;

    console.log('====================================');
    console.log('Total requests: ', this.totalRequests);
    console.log('Payload size: ', this.payloadSize);
    console.log('Payload type: ', this.payloadSizeType);
   }, 1);
  }

If that works, then the cause of you not seeing the updates in your user interface was because change detection wasn't being triggered properly when your web socket received a value. Angular's change detection works through the use of Zone.js, which wraps all asynchronous behaviour so that Angular can be notified each time an asynchronous function completes. If the setTimeout works, then Zone.js wasn't properly trapping the websocket async behaviour.

There are more elegant ways of resolving the issue through the use of the NgZone service, but the setTimeout can be a quick fix.

Comments

1

My solution to this was not quite what the two answers provided, but they pointed me in the right direction.

First of all, I was creating my websocket connection in the AttackComponent, and I would rather create it in my AppComponent so that the connection can be accessed globally (not related to my question).

I created an interval function that checks to see is the socket has connected, and if so then it invokes a function call, which would be a callback function that I pass in from my AttackComponent.

That callback is passed in via an arrow function in the AttackComponent, which gives me the right this context. I then call another method to set the sockets onmessage property to my AttackComponent's method, and I bind it to the proper this context which is allowed by the arrow function. I could also just do the binding outside of that all together.

Here is en example of what I did, that now works and updates my template properly:

WebSocketService:

  setSocketOnMessage(fn: Function): void {
    this.socket.onmessage = function (message) {
      fn(JSON.parse(message.data), message);
    };
  }

  onReady(cb: Function): void {
    let socketInterval = setInterval(() => {
      if (this.isConnected()) {
        cb();

        clearInterval(socketInterval);
      }
    }, 50);
  }

AttackComponent:

  constructor(private webSocketService: WebSocketService) {
    this.webSocketService.onReady(() => {
      this.webSocketService.setSocketOnMessage(this.sOnMessage.bind(this));
    });
  }

  sOnMessage(data: any): void {
    this.hasReceivedMessage = true;
    this.totalRequests = data.total_requests;
    this.payloadSize = data.payload_size;
    this.payloadSizeType = data.payload_size_type;
  }

Comments

0

I had a similar problem. In my case the websocket is injected into the component so it may not be the same solution. Essentially I used the approach described here (https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html#observables)

constructor(private cd: ChangeDetectorRef) {

and then when I get the message...

this.socket$.subscribe( (message: Message) => {
    this.messages.push(message);
    this.cd.markForCheck();  // mark the path from our component until root to be checked for the next change detection run.
 }

Hope this helps

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.