ViewModel Design Considerations #9
Andrei15193
announced in
Guides and Tutorials
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Model-View-ViewModel is a user interface architectural pattern that aims to separate the presentation from the business logic. Everything inside the view is considered as part of presentation, a ViewModel is an object that exposes some data and methods used by the presentation layer.
The ViewModel is observable and notifies watchers of any change to the exposed properties. Calling a method, such as
loadAsync
can do this. We typically display a spinner or a progress bar when loading data and while a ViewModel is doing this it marks itself as "busy". ViewModels work with the Model, they load data from it and eventually update the Model.On the other hand, the Model is loosely defined, everything that is not part of the View or is not a ViewModel is part of the Model. This is to avoid making restrictions on how the domain is modeled as MVVM is primarily concerned with presentation and separating the presentation logic from the business logic.
We can employ any type of architecture for the Model and still use MVVM, there are no restrictions.
The need to this additional layer becomes more obvious when we start working with the same entities in different view. We display the same things, but differently. For instance, we can show a list of cities for which we would use a
CityListViewModel
or aCityTableViewModel
while when we edit the details of a city we would use aCityEditFormViewModel
combined withFormFieldViewModel
s.From my experience with this pattern, the quality of an MVVM implementation rests mostly on the design quality of ViewModels. The better we design them, the easier it will be for us maintain and extend the application.
1. Event Subscription Management
Events are implemented using the Observer pattern. The subject registers observers, maintains an active reference towards each, through the lens of an interface, this is an example of dependency inversion as well, and notifies them of any changes. The observer may, or may not react to this change.
A potential issue here is that the subject maintains an active reference. For instance, if the subject is a singleton, it lives for as long as the application lives, then any observer that has subscribed to it but is no longer being used is actually a memory leak. The application will continue to create instances of the observer and they subscribe to the singleton instance whenever they are needed, by not unsubscribing the subject will keep the "lost" observers alive because the subject is kept alive.
This is a crucial aspect of event subscription. Whenever an observer subscribes to an event, we must ensure that the opposite action is done, unsubscribing, once the observer is no longer needed.
The only exception to this rule is when both subject and observer have the same lifespan. If and only if discarding observers is done when discarding their subject, then we do not need to worry about unsubscribing from events.
This is the reason why composition and event subscription work great together.
When it comes to React Hooks, we can combine the subscribe and unsubscribe behavior in a single place ensuring that the component is subscribed to the subject throughout its lifespan and a moment more or less. This will avoid any accidental memory leaks.
2. Prefer ViewModel Composition
The Unified Modeling Language (UML) defines several associations we can have between two types.
Prefer ViewModel composition whenever one is subscribing to the events of another. This will simplify event subscription management and reduce the chance of accidental memory leaks.
Common examples for this are form fields, the validity of a form is determined by the validity of each field. This the reason why fields can be registered and unregistered from a
FormFieldCollectionViewModel
. It may be a rare case when we really need to unregister fields, however React MVVM (react-model-view-viewmodel
) covers this case as well.3. Separation of Concerns
This may go hand in hand with SOLID principles, it may tie to other design principles or patterns as well. Splitting up login into multiple ViewModels is beneficial and simplifies overall design and eases maintenance.
For instance, we may be tempted to design a ViewModel for a form that can handle both loading/initialization as well as submitting said form. From what I have observed, this sounds like a good idea, however it overcomplicates a single object.
What I have found to work is to split this into multiple ViewModels. One that handles form initialization and submission of a form, this would be the "connected" (as in, API connected) ViewModel that can go through multiple states as it performs asynchronous operations, and one, or actually more, that represents the form. The form itself is "disconnected" from the API.
Connected and Disconnected ViewModels
This leads up to distinction between ViewModels. Connected ViewModels perform API calls through asynchronous operations and thus go through a number of states that are visible on the user interface. Whenever we do something that may take a bit of time we display a spinner or a progress bare, something that lets the user know that their request is being handled and may take a bit of time.
Disconnected ViewModels are "offline" or handle user interaction without knowing what is going on in the backend or that database. The operate on a copy of the data that was retrieved by a Connected ViewModel. Examples for these are forms and grids.
We have one ViewModel that fetches and communicates with the API and another set of ViewModels that allow the user to interact with the data. The former is "connected" while the latter are "disconnected". Each are concerned with one thing and one thing only.
Closing Thoughts
There is probably a lot more that can be added as design considerations when it comes to the Model-View-ViewModel pattern, however I did not want to make this longer than it has to be. I've tried to concentrate on the key aspects that have improved MVVM implementations that I have made over the years that are generally applicable.
Beta Was this translation helpful? Give feedback.
All reactions