F6.07 · part 2 of 3
Subscribing a LiveView
A LiveView receives broadcasts the same way a GenServer receives any message: in
handle_info/2. The two halves are subscribe and
handle. You subscribe on the connected mount — behind the
connected?/1 guard from F6.06.1, so the throwaway first paint does not join — with
one call to the facade’s Portal.subscribe("courses"). From then on, any message
broadcast on that topic lands in the LiveView process’s mailbox and is delivered to
handle_info/2, which pattern-matches the event tuple, updates the socket assigns, and
returns {:noreply, socket} — triggering the usual re-render and diff. This is the
distinction worth holding onto: handle_event/3 handles messages from the
browser (a click, a keystroke), while handle_info/2 handles messages from
other processes (a PubSub broadcast, a timer, a task result). Both end the same way — new assigns,
a re-render — but they are different doorways into the LiveView, and keeping them straight is most of
understanding real-time updates. Paired with streams from F6.06.3, a single broadcast inserts one row into every
connected client.
Four parts: subscribe versus the two handlers, the path a broadcast takes into the LiveView, the mount-and-handle code, and driving a stream from a broadcast.
Two doorways
A LiveView reacts to two kinds of message. subscribe opts it into a topic.
handle_info/2 receives process messages — PubSub broadcasts, timers.
handle_event/3 receives browser events — clicks, input. Select one.
Subscribe and handlers · select one
callback: subscribe
handles: join a topic on connected mount
Mixing the two up is the classic beginner bug: trying to catch a broadcast in
handle_event, or a click in handle_info. The cure is the one
question — did this come from a browser or a process?
A broadcast arrives
The broadcast from F6.07.1 is sent on "courses". Because this LiveView subscribed,
the message is placed in its mailbox and the runtime calls handle_info/2 with it. The
handler updates assigns and returns {:noreply, socket}; render runs; the diff goes to
this client’s browser. The same arc as a browser event, entered through the other door.
PubSub → mailbox → handle_info → diff
Nothing in the broadcast knows or cares which LiveViews are listening. The publisher fires once; the runtime fans the message out to every subscribed mailbox. Adding a tenth viewer changes nothing in the publishing code.
Subscribe and handle
mount/3 subscribes once, on the connected pass. handle_info/2
matches {:course_created, course} and prepends it to @courses
with update/3. A new course created anywhere — by this user or another —
now appears at the top of this list without a reload.
def mount(_params, _session, socket) do if connected?(socket), do: Portal.subscribe("courses") {:ok, assign(socket, courses: Portal.list_courses())} end # a broadcast arrives as a process message, not a browser event def handle_info({:course_created, course}, socket) do {:noreply, update(socket, :courses, fn courses -> [course | courses] end)} end
update/3 transforms one assign from its current value, which is cleaner than
reading and re-assigning. It is also exactly how you would evolve a GenServer’s state — the LiveView
is that pattern with a screen attached.
Updating a stream
With the stream from F6.06.3, the broadcast updates the DOM directly instead of a list in memory.
stream_insert/3 with at: 0 prepends the new row; a separate
:course_updated clause replaces a row in place by id. Every connected client runs
the same handler, so the feed updates for all of them at once.
# with streams (F6.06.3): insert one row in every client's DOM def handle_info({:course_created, course}, socket) do {:noreply, stream_insert(socket, :courses, course, at: 0)} end def handle_info({:course_updated, course}, socket) do {:noreply, stream_insert(socket, :courses, course)} # replaces the row by id end
subscribe once
On the connected mount, behind connected?/1.
handle every event
handle_info/2 updates a stream; all clients update together.
Next: channels & presence — the primitive beneath LiveView, and tracking who is on a topic so you can show a live viewer count.