Let's say I am trying to follow the Elm architecture and split my workflow into User
s and Invoice
s while using StartApp
.
Users have invoices, but they must log in to access them.
The model may look like something along these lines:
type Model
= NotLoggedIn Credentials
| LoggedIn RealName (Maybe Invoices)
type alias State =
{ login : Model
, notification : ......
, ......
type alias Invoices = { invoices: List Invoice, ...... }
User module has actions:
type Action
= Login (Result Http.Error String)
| Logout
| Submit
...
and update function:
update : Action -> Model -> (Model, Effects Action, Notification)
update action user =
case (action, user) of
(Login res, _) ->
case res of
Ok name ->
(LoggedIn name Nothing, Effects.none, Info "Welcome!")
...
I skip the details of authentication, it's all good. The interesting part is Login
action. The tuple is sent to the step
function in main
:
step : Action -> State -> (State, Effects Action)
step action state =
case action of
UserAction a ->
let (newstate, ef, n) = User.update a state.login
in ({ state | login = newstate, notification = n }, Effects.map UserAction ef)
InvoiceAction a -> ......
So the user has logged in. Next we want to call some init
action in the Invoice
module.
But how should this be done properly? How to initiate the action of Invoice
to preserve incapsulation? Shall I return sometihng other than Effects.none
?
This might be a case that could be solved by different modeling of your app's data.
The way I understand it, you have actions that require as user and actions that do not require an user. The InvoiceAction seams to me that it should belong to the UserAction.
So, you could have
type MainAction = UserAction UAction | NonUserAction NonUAction
type UAction = AuthAction Credentials | InvoiceAction Invoice.Action
The user model would encapsulate both login details and invoice details. And then, after a successful log in you could redirect to an InvoiceAction.
update action model =
case action of
AuthAction credentials ->
let
(isLoggedIn, notifications) = Authentication.check credentials
model' = { model | credentials = credentials, notifications = notifications}
in
if isLoggedIn
then update (Invoice.initialize model'.credentials) model'
else (model', Effects.none)
InvoiceAction act ->
let
(invoices, fx) = Invoice.update model.credentials act model.invoices
in
({model | invoices = invoices}, Effects.map InvoiceAction fx)
The actual action is provided by the Invoice module via a function initialize
with a signature like initialize: Credentials -> Action
. This is done to maintain encapsulation. The User module need not know about particular Invoice actions, only that there is one related to initialization and it can get it via that function.
Also, please note that I've simplified the update signature and moved the notifications inside the model. This is a personal preference as I see notifications as nothing special. They are like any other piece of data in the model. Of course, if the notifications are tasks that get routed via some custom StartApp into a port and get displayed by some JS mechanism, it might make sense to keep them in the return.
One approach will be:
User
updateInvoice
This chapter in elm-tutorial shows this pattern.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With