TL;DR Remove all functions in Window and replace with a single subscription.
The Window package basically has two functions: size : Task x Size and resizes : (Size -> msg) -> Sub msg.
The way I always end up using Window in Elm is returning Task.perform WindowSize Window.size in init, and Window.resizes WindowSize in subscriptions.
Now to the thing I wanted to discuss.
Why do we even use Window? Because some calculation depends on the window width. Not the window width at startup. The window width at any given time.
So why not exposing just a single function, size : (Size -> msg) -> Sub msg, that produces a value immediately as well as when the window resizes?
No need to set up both an initial Cmd and a subscription. Easier to get going! Also no need for beginners to look up Task.perform.
Impossible to make the mistake of only measuring at startup.
This seems like a good idea to me, though admittedly I only have one data point to contribute. I’m definitely doing exactly this two-step dance in my init function here and my subscriptions function here. The code savings (in terms of lines of code) is minor, but I think the conceptual simplification is worthwhile.
I don’t bother with Window.size. The mental model that I have for the init function is that it returns two values: the information I need to start the app (the Model), and the information I request when I start the app but don’t need immediately (the Cmd). The window size falls in the former in my mental model, so I don’t request it. The solution I have arrived to is to pass it as a flag, alongside other information I need immediately like: today’s date, tokens, API URLs, random Int for Generators, etc.
Layout decisions like the presence of parts, or column numbers in my app is determined by the size of the window, so I have to store this information in the model. I use style-elements in my project, and they offer the Device type to help me make layout decisions. To initialize Device, I need to give it the window’s dimensions.
I do use Window.resizes. As the argument, I pass it the composition of Element.classifyDeviceand my message constructor. This is great to update layout when the dimensions change, but it does not help me initialize the window size in my model, which is the same problem I have Window.size, so I use flags to solve that particular problem.
My use case is exactly the same as this one. I have a Device type that gets updated each time the window resizes. It is initialized with flags at the beginning. Then responsive layout is entirely done using style-elements depending on this Device. Sorry @lydell if I wasn’t too verbose previously. If you are interested in the corresponding code, the app in question is this demo app. And the code is on github.
One thing you will notice if you go check the code, is that instead of window.innerWidth, I’m using document.documentElement.clientWidth because I’m interested in layout viewport, not visual viewport. So I do not use Window.resizes but my own port (in index.html), but that’s just implementation detail.
Yes, it is annoying to need both a command and a subscription to do this. More annoying for the pedantically inclined is that the specification says nothing about the order in which messages will be delivered. What if the window is in the midst of resizing when the app is initializing. Is there a chance that the command result will arrive in some sequence with the subscription results that results in the wrong final value being delivered? Reportedly no, but the documentation is silent.
As for why one needs both, the issue is that issuing a command is a concrete event at the end of an update cycle and as such can trigger immediate activity. Subscriptions are just a measure of interest in a particular event and once functions to translate the underlying data become involved cannot be readily compared to one another to tell whether the subscription being returned now is a new subscription or is simply the current instance of an existing subscription. For example, if one were to write (this is contrived):
subscriptions model =
Window.resizes (WindowResized model.resizeCount)
then each time the runtime calls subscriptions it will build a new tagger function which will make the subscription look different from the previous one. In fact, it will be different whenever resizeCount changes. So, should it then deliver a result or not? Now, as I said, this is contrived in that one should probably wait for the update code to look at the model properties but the point is that it becomes impossible in practice to tell when two subscriptions are the same which is a pre-requisite to being able to fire an event when a subscription is created.
This is actually an interesting problem, not for window sizing where the command plus subscriptions is a bit awkward but relatively straightforward, but for what one would do if one wanted a way to cancel tasks. The HTTP progress module demonstrates that subscriptions can be used to provide cancelation for HTTP requests and as such could provide a pattern for canceling tasks or at least task chain execution on loss of interest. But note that it requires clients to supply unique identifiers for the subscriptions to address the issues above and these unique identifiers are themselves potentially awkward to generate unless one has natural identifiers or a readily accessible centralized source for such identifiers. The subscription handling code also has to decide what to do in the event that the other parameters to the subscription change without the identifier changing. What one almost wants is a command to create a subscription since the command execution essentially provides identity to the subscription.