Home > Software engineering >  Angular components updating from web socket messages
Angular components updating from web socket messages

Time:11-08

I'm trying to get and update data in my frontend angular app from a web socket written in swift vapor. The backend is also written by me, so I can edit anything. When frontend first connects the socket sends all needed data (in this case a big JSON object containing sub-objects which in turn have sub objects etc...).

{
  "alpha": {
    "isUsingXXX": true,
    "loopbacks": [
      "10.50.255.1",
      "10.50.255.11"
    ],
    "hasLte": false,
    "isUsingLte": false,
    "virtualLink": {
      "links": {
        "Bravo <-> XXX": {
          "gradeUpdates": {
            "up": {
              "174": {
                "message": "Test message",
                "grade": "BAD",
                "progressive": 174,
                "date": "2022-11-03T08:13:54Z"
              }
            },
            "down": {
              "175": {
                "message": "Test message",
                "grade": "BAD",
                "progressive": 175,
                "date": "2022-11-03T08:13:54Z"
              }
            }
          },
          "congestionUpdates": {
            "173": {
              "message": "Test message",
              "congestion": "CONGESTED",
              "progressive": 173,
              "date": "2022-11-03T08:13:54Z"
            }
          },
          "statusUpdates": {
            "7": {
              "status": "ACTIVE",
              "message": "Setting initial value",
              "progressive": 7,
              "date": "2022-11-03T08:13:54Z"
            }
          },
          "name": "Bravo <-> XXX"
        },
        "Alpha <-> XXX": {
          "gradeUpdates": {
            "up": {
              "170": {
                "message": "Test message",
                "grade": "GOOD",
                "progressive": 170,
                "date": "2022-11-03T08:13:54Z"
              }
            },
            "down": {
              "171": {
                "message": "Test message",
                "grade": "GOOD",
                "progressive": 171,
                "date": "2022-11-03T08:13:54Z"
              }
            }
          },
          "congestionUpdates": {
            "4": {
              "message": "Setting initial value",
              "congestion": "UNCONGESTED",
              "progressive": 4,
              "date": "2022-11-03T08:13:54Z"
            }
          },
          "statusUpdates": {
            "3": {
              "status": "ACTIVE",
              "message": "Setting initial value",
              "progressive": 3,
              "date": "2022-11-03T08:13:54Z"
            }
          },
          "name": "Alpha <-> XXX"
        }
      },
      "gradeUpdates": {
        "176": {
          "message": "Test message",
          "grade": "BAD",
          "progressive": 176,
          "date": "2022-11-03T08:13:54Z"
        }
      }
    },
    "statusUpdates": {
      "1667463234643": {
        "status": "SAILING",
        "date": "2022-11-03T08:13:54Z"
      }
    },
    "pings": {
      "1667463234643": {
        "latency": 675,
        "date": "2022-11-03T08:13:54Z"
      }
    },
    "hasXXX": true,
    "name": "Alpha",
    "codename": "alpha"
  },
  "bravo":{...},
  "charlie":{...}
}

Now, what I'm trying to achieve is the update of single sub objects with subsequent socket messages, which are something like

{
  "kind":"linkGradeUpdate",
  "codename": "alpha",
  "linkName": "Bravo <-> SantaRosa 107",
  "direction": "up",
  "data":{
    "201": {
      "message": "Update test message",
      "grade": "GOOD",
      "progressive": 201,
      "date": "2022-11-03T08:15:54Z"
    }
  }
}

I mean I'm trying to send just necessary data to perform the update adding them to the main received big object. And everything in my code works except for UI, which I cannot figure out. I tried to make a component which loops through the data with sub components but I cannot get get data displayed, do I need an Observable for that?

import { Injectable } from '@angular/core'
import { Ship, Message } from '../interfaces'

@Injectable({
  providedIn: 'root'
})
export class WebsocketService {
  url = 'ws://localhost:8080/api/socket/session'
  socket: WebSocket
  ships: Map<string, Ship> = new Map()

  constructor() {}

  openSocket() {
    this.socket = new WebSocket(this.url)
    this.socket.onopen = event => {
      console.log(`Open ${event}`)
    }
    this.socket.onmessage = event => {
      const message = JSON.parse(event.data)
      if (this.ships.size == 0) {
        console.log('Inititalizing ships...')
        this.ships = new Map(Object.entries(message))
      } else {
        console.log('Updating ship...')
        this.updateShips(message)
      }
    }
    this.socket.onclose = event => {
      console.log(`Closed ${event}`)
    }
  }

  updateShips(message: Message) {
    let ship: Ship = this.ships.get(message.shipCodename)
    const data = message[message.kind]
    const linkName = message.linkName
    switch (message.kind) {
      case 'shipStatus':
        console.log('Before update')
        ship.statusUpdates[data.progressive] = data
        console.log('After update')
        break
      case 'virtualLinkGrade':
        ship.virtualLink.gradeUpdates[data.progressive] = data
        break
      case 'linkGrade':
        const linkDirection = message.linkDirection
        if (linkDirection == 'UP') {
          ship.virtualLink.links[linkName].gradeUpdates.up[data.progressive] = data
        } else {
          ship.virtualLink.links[linkName].gradeUpdates.down[data.progressive] = data
        }
        break
      case 'linkStatus':
        ship.virtualLink.links[linkName].statusUpdates[data.progressive] = data
        break
      case 'linkCongestion':
        ship.virtualLink.links[linkName].congestionUpdates[data.progressive] = data
        break
    }
  }

  closeSocket() {
    this.socket.close()
  }
}
import { Component, OnInit } from '@angular/core'
import { WebsocketService } from 'src/app/services/websocket.service'
import { Ship } from 'src/app/interfaces'

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.page.html',
  styleUrls: ['./dashboard.page.scss']
})
export class DashboardPage implements OnInit {
  ships: Map<string, Ship>

  constructor(private websocket: WebsocketService) {}

  ngOnInit() {
    this.websocket.openSocket()
    this.ships = this.websocket.ships
    console.log(this.ships)
  }

  ngOnDestroy() {
    this.websocket.closeSocket()
  }
}

Now, I understand that data is updated after ngOnInit(), so I can see data in console but not in the rendered view. My question is: do I need an Observable<Map<string, Ship>>, or Map<string, Observable<Ship>> to re-render just updated chunks of data? Or maybe something totally different... Thanks, I hope I've been clear enough...

CodePudding user response:

In your service you are reassigning the ships property: this.ships = new Map(Object.entries(message)), but you only grab the contents of this property once in the ngOnInit function of the component that uses it.

To make sure that your component can get the updated ship data, you could make the ships property have a type Subject (similar to Observable), then subscribe to it in your component. Here's some code that should point you in the right direction:

Service

export class WebsocketService {
  ships: Subject<Map<string, Ship>> = new Subject();

  private shipData: Map<string, Ship> = new Map<string, Ship>();

  constructor() {}

  openSocket() {
    // ...

    this.socket.onmessage = event => {
      const message = JSON.parse(event.data)
      if (this.ships.size == 0) {
        console.log('Inititalizing ships...')
        this.shipData = new Map(Object.entries(message));
        this.ships.next(this.shipData);
        //         ^^^^ <= key part
      } else {
        console.log('Updating ship...')
        this.updateShips(message)
      }
    }
  }

  updateShips(message: Message) {
    let ship: Ship = this.shipData.get(message.shipCodename)

    // ...

    this.ships.next(this.shipData);
  }
}

Note: for the updateShips method to work, we need to store a copy of the last ship data in the service, and send it through the Subject when it changes.

Component

  ships: Map<string, Ship>
  shipsSubscription: Subscription;

  constructor(private websocket: WebsocketService) {}

  ngOnInit() {
    this.websocket.openSocket()
    this.shipsSubscription = this.websocket.ships.subscribe(ships => {
      this.ships = ships;
      console.log(this.ships)
    });
  }

  ngOnDestroy() {
    this.websocket.closeSocket()

    // It's a good idea to remove subscriptions when the component is destroyed
    if (this.shipsSubscription) {
      this.shipsSubscription.unsubscribe();
    }
  }

This will ensure that your component gets the latest ships data whenever it it sent from the server.

  • Related