Yes, it is possible listen to a WebSocket inside a service and notify the messages received to Components via Observables.
In my opinion this is a very natural use case for Observables.
You can open the websocket connection when you launch the app and, if it is a service provided in root, you can share the same service instance across different components.
This function returns an Observable that notifies and instance of WebSocket when a websocket connection is opened
function openSocket$(url: string) {
return new Observable(
(subscriber: Subscriber<WebSocket>): TeardownLogic => {
const conn = new WebSocket(url);
conn.onopen = () => {
subscriber.next(conn);
subscriber.complete();
};
conn.onerror = (err) => {
console.error("Websocket errored while trying to connect", err);
subscriber.error(err);
};
conn.onclose = (ev) => {
console.log("Websocket closed before having emitted any message");
subscriber.complete();
};
}
);
}
This function requires an instance of WebSocket as input and returns an Observable which notifies each message received on the websockets channel:
function messages$(socket: WebSocket) {
// if the onmessage property is defined, it means that this websocket is already in use by
// some component which is interested in its message stream
if (socket.onmessage) {
throw new Error(
"Websocket has already a function set to manage the message stream"
);
}
return new Observable(
(subscriber: Subscriber<MessageEvent>): TeardownLogic => {
socket.onmessage = (msg: MessageEvent) => {
subscriber.next(msg);
};
socket.onerror = (err) => {
console.error("Websocket errored while sgreaming messages");
subscriber.error(err);
};
socket.onclose = () => {
console.log("Websocket closed");
subscriber.complete();
};
return () => {
// clean the onmessage callback when the Observable is unsubscribed so that we can safely check
// whether the onmessage callback is null or undefined at the beginning of this function
socket.onmessage = null;
};
}
);
}
You can see an example of app that uses this mechanism here.