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.