Reputation: 617
I am new to svelte, I am trying to get temperature using websocket below is the code for the used to get the temperature using websockets.
ws.js
const webSock = () => {
let socket = new WebSocket("ws://localhost:54321/webTest");
let temperature = 0;
const getTemperature = () => {
return temperature;
}
socket.onopen = function (e) {
console.log("[open] Connection established");
console.log("Sending to server");
};
socket.onmessage = function (event) {
var message = JSON.parse(event.data);
temperature = data.message;
console.log(temperature);
};
socket.onclose = function (event) {
console.log(event.reason);
};
socket.onerror = function (error) {
console.log(`[error] ${error.message}`);
};
const sendMessage = () => {
var msg = {
'data': 'hello'
};
console.log(msg);
socket.send(JSON.stringify(msg));
}
return {getTemperature};
};
export default webSock;
Below is the code on App.svelte
<script>
import WS from "./ws.js";
const ws = WS();
$: temperature = ws.getTemperature();
</script>
<main>
<h1>{temperature}</h1>
</main>
Web page displays zero which is the initial value and doesn't change further however in console in the web browser I am able to get the temperature because of the console log statements
Kindly point me in the right direction to fix this issue.
Thank You
Upvotes: 1
Views: 3361
Reputation: 25001
Nice little ws module :)
So, the thing is your ws.getTemperature()
method is not reactive. And so, although, you're using in a reactive expression $:
Svelte won't know when the value has changed, so it will run this only once.
You need to propagate not only the value, but also the changes.
In old school JS, we'd do that with a callback. You can adapt your code like this for example:
let _callback
const getTemperature = callback => {
_callback = callback // <- save a callback for future change
callback(temperature)
}
socket.onmessage = function (event) {
var data = JSON.parse(event.data);
temperature = data.message;
console.log(temperature);
if (_callback) _callback(temperature)
};
In your Svelte component, you can then subscribe to this source of data:
<script>
import WS from "./ws.js";
const ws = WS();
let temperature
ws.getTemperature(value => {
temperature = value // <- Svelte will see that, it's reactive!
})
</script>
<main>
<h1>{temperature}</h1>
</main>
Here the changes will be propagated, because we're assigning to a top level variable of the component. The assignment (=
) is what is used in Svelte to notify that a value has changed.
This code will work for what you want to do. Now, in Svelte, you can use a store. Stores are essentially some kind of simplified streams (or Observable as they're called in ES), that is they represent a value over time. Same as our callback example, except they offer a handful of other very useful tool (like computing derived values from other stores), and also a slick syntax in the Svelte component. Stores are the idiomatic Svelte way when you need to import "reactivity" from your normal JS sources. See the docs for full reference.
Here's how we would rewrite our callback example with a store instead:
// writable are the simplest form of stores
import { writable } from 'svelte/store'
const webSock = () => {
let socket = new WebSocket("ws://localhost:54321/webTest");
// our temperature is now a store with initial value 0
const temperature = writable(0);
// now we don't need to change this function, the change will be propaged
// by the store itself
const getTemperature = () => {
return temperature;
}
socket.onmessage = function (event) {
var data = JSON.parse(event.data);
// temperature = data.message;
// we update the value of our (writable) store,
// this will propagate the change
temperature.set(data.message)
};
// ... rest of the code
return { getTemperature };
};
export default webSock;
In your Svelte component, you can then use your store with the special $
prefix syntax, to access the value of the store ('cause the temperature
variable is a reference to the store itself, it's just a mean to our end, the result we need is the value):
<script>
import WS from "./ws.js";
const ws = WS();
const temperature = we.getTemperature()
console.log(temperature) // log the store, for the fun
// but what we want is the value, that we access with the special $ syntax
$: temp = $temperature
// for debug: this will log the value every time it changes
$: console.log(temp)
</script>
<main>
<!-- you can use the $ prefixed value directly in the template -->
<!-- (so we actually don't need the reactive expression above, in this example) -->
<h1>{$temperature}</h1>
</main>
So, very well, our code is leaner... But that is not all! Svelte store also have a very handy feature to deal with disposal of resources. Namely, you open a WS: you will need to close it at one point. Svelte stores can help with that.
In effect, the $
syntax we've seen above will actually setup a subscription to the store. And the subscription will be cancelled when the component is destroyed. If the store is subscribed by multiple components, the store will be disposed only when the last component unsubscribes (it will be reinitialized, if a new subscription is made). This is very handy to manage the lifecycle of disposable things in your code.
In order to use this, we need to use a little more advanced readable
store. Here's your example updated for this:
import { readable } from 'svelte/store'
// a readable store with initial value 0
//
// we pass it a function; the first argument of the function will let us update
// the value when it changes
//
export const temperature = readable(0, set => {
// this function is called once, when the first subscriber to the store arrives
let socket = new WebSocket("ws://localhost:54321/webTest");
socket.onmessage = function (event) {
var data = JSON.parse(event.data);
// we're using the `set` function we've been provided to update the value
// of the store
set(data.message)
};
// ... the rest of your socket code
const dispose = () => {
socket.close()
}
// the function we return here will be called when the last subscriber
// unsubscribes from the store (hence there's 0 subscribers left)
return dispose
})
The consumer component will barely change from our last example. The only difference is the way we'll get a reference to our store (since now the store is exported from the JS module):
<script>
import { temperature } from './ws.js'
// log the value
$: console.log($temperature)
// draw the rest of the owl
</script>
...
Here! Now your whole WS logic can be encapsulated in your JS module. That makes for good separation of concern. And your WS lifecycle is also managed automatically by Svelte! (And we've covered 75% of the store topic... They're efficient, to the point... And simple!)
Note I haven't deeply checked the code I've pasted from your example, and it seems that is has some little mistakes, but I'm sure you'll get the general idea and you can fix those by yourself.
Upvotes: 18