⚡️ Communicate with Phoenix Channels & sync state with mobx
MIT License
LiveViewModel is an Elixir library for building interactive web and mobile applications with a focus on real-time, event-driven architecture. It offers an alternative approach to state management and client-server communication, particularly suited for applications that require real-time updates and don't rely on server-side HTML rendering.
This project is currently under active development. The API and features are subject to change.
Use with caution in production environments.
LiveViewModel.Channel
: A behavior module for creating Phoenix channels that handle LiveViewModel logic.LiveViewModel.Encoder
: A protocol for customizing how data is encoded before being sent to clients.LiveViewModel.Event
: A struct representing events that can be sent from the server to clients.LiveViewModel.MessageBuilder
: A module for creating state change and patch messages.LiveConnection
: Manages the connection to the server and provides methods for joining channels and sending events.LiveViewModel
: A decorator and base class for creating view models that sync with the server state.@liveObservable
, @localObservable
, @action
, @computed
, @liveEvent
, @liveError
) for defining reactive properties and methods.LiveViewModel is particularly well-suited for:
Add LiveViewModel to your dependencies in mix.exs
:
defp deps do
[
{:live_view_model, "~> 0.2.0"}
]
end
Create a channel using LiveViewModel.Channel
:
defmodule MyAppWeb.MyChannel do
use LiveViewModel.Channel, web_module: MyAppWeb
@impl true
def init(_channel, _payload, _socket) do
{:ok, %{count: 0}}
end
@impl true
def handle_event("update_count", %{"value" => value}, state) do
{:noreply, %{state | count: value}}
end
end
Add the channel to your socket in lib/my_app_web/channels/user_socket.ex
:
defmodule MyAppWeb.UserSocket do
use Phoenix.Socket
channel "room:*", MyAppWeb.MyChannel
# ... rest of the socket configuration
end
Install the npm package:
npm install live-view-model
Create a view model:
import { liveViewModel, LiveConnection, liveObservable, liveEvent } from "live-view-model";
@liveViewModel("room:lobby")
class MyViewModel {
constructor(private conn: LiveConnection) {}
@liveObservable()
count: number = 0;
@liveEvent("update_count")
updateCount(value: number) {
return { value };
}
}
Connect and use the view model:
import { connect, join } from "live-view-model";
const conn = connect("ws://localhost:4000/socket");
const viewModel = new MyViewModel(conn);
join(viewModel);
autorun(() => console.log('Count changed:', viewModel.count));
viewModel.updateCount(5);
viewModel.updateCount(4);
@liveViewModel(topic: string)
Sets up a class as a live view model, connecting it to a specific Phoenix channel.
Usage:
@liveViewModel("room:lobby")
class LobbyViewModel {
// ...
}
Functionality:
@liveObservable(serverKey?: string)
Marks a property for synchronization with the server and integrates with MobX to create observable properties.
Usage:
@liveObservable("server_count")
count: number = 0;
@liveObservable.deep()
messages: ChatMessage[] = [];
Functionality:
@liveObservable.ref
: Creates a reference observable@liveObservable.struct
: Creates a structural observable@liveObservable.deep
: Creates a deep observable@liveObservable.shallow
: Creates a shallow observable@localObservable()
Marks a property as a local observable, not synchronized with the server.
Usage:
@localObservable()
localCount: number = 0;
@localObservable.ref()
localReference: SomeType | null = null;
Functionality:
@localObservable.ref
: Creates a reference observable@localObservable.struct
: Creates a structural observable@localObservable.deep
: Creates a deep observable@localObservable.shallow
: Creates a shallow observable@liveEvent(eventName: string)
Defines a method that sends events to the server when called.
Usage:
@liveEvent("notify")
notify(message: string) {
return { message };
}
Functionality:
@liveError()
Specifies an error handler for the view model.
Usage:
@liveError()
handleError(error: any) {
console.error("View model error:", error);
}
Functionality:
@action()
Alias for MobX action decorator.
Usage:
@action()
setCount(count: number) {
this.count = count;
}
Functionality:
@computed()
Alias for MobX computed decorator.
Usage:
@computed()
get messageCount() {
return this.messages.length;
}
Functionality:
LiveViewModel.Encoder
protocol to customize how data is serialized before being sent to clients.The library includes LiveViewModel.TestHelpers
module for writing tests for your LiveViewModel channels.
LiveViewModel integrates seamlessly with React using mobx-react-lite for efficient rendering and state management. Here's an example of how to use LiveViewModel in a React component:
npm install live-view-model mobx mobx-react-lite react
import React, { useMemo, useEffect } from 'react';
import { observer } from 'mobx-react-lite';
import { connect, join, leave } from 'live-view-model'';
const App = () => {
const conn = useMemo(() => {
return connect('ws://localhost:4000/socket');
}, []);
return (
<LobbyComponent conn={conn} />
);
}
const LobbyComponent = observer(({ conn }) => {
const vm = useMemo(() => {
return new LobbyViewModel(conn);
}, [conn]);
useEffect(() => {
join(vm);
return () => leave(vm);
}, [vm]);
return (
<div>
<h1>Lobby</h1>
<p>Count: {viewModel.count}</p>
<button onClick={() => viewModel.increment()}>Increment</button>
<button onClick={() => viewModel.decrement()}>Decrement</button>
</div>
);
});
export default App;
While LiveViewModel shares similar goals with Phoenix LiveView, it takes a different approach:
This distinction allows LiveViewModel to be used in scenarios where full server-side rendering is not possible or desirable, such as in native mobile applications.
Contributions are welcome! Please feel free to submit a Pull Request.