MVVM: Exploring a Pattern
Visualizing data has always been a rather elusive challenge for software development. It seems easy until we get to some more complex scenarios and we realize that just putting data in our views is not enough and we need more structure in our presentation layer. We’re still struggling with this today.
This is an old story of mine. Ever since I learned about Windows Presentation Foundation (WPF) in a presentation alongside the Model-View-ViewModel (MVVM) pattern I was curious about it. This happened somewhere in 2012, if my memory still holds. WPF itself was built around this pattern with XAML and its data binding. A pattern that decouples the User Interface (UI) behaviour from presentation. In one part, you have data and behaviour and on the other you have just plain presentation. The latter consumes data from the former and calls commands to get things done. A clear separation of concerns leaving the ViewModel completely separated from the View. This allows ViewModels to be tested with ease and then integrate the two. Freshness and beauty in the UI architectures, something to look forward to.
Since then I wanted to try this pattern and fully understand it because from the presentation itself it was clear that it would make life easier when it came to UI, or better said, front-end work. Stuff that has to do with how users interact with an application. I’ve been through trials and errors with this pattern, I tried a lot of things to make it work with my very limited understanding of it at the time. These tries, each has taught me something that I did not know about MVVM or helped me better understand the pattern.
Initially, I have thought of it to be some sort of mapping or transformation between the Model and the ViewModel. After all, there is the word “Model” in “ViewModel”. The Model was nothing more than a few plain old C# objects (POCOs), simple Data Transfer Objects (DTOs) that were loaded from a repository and passed to the ViewModel. The ViewModel was an observable version of the Model, a 1-to-1 mapping between the types, only that ViewModels were observable and upon any change they would update the backing model objects. After all, that’s what the repository knew and that’s how it would know to store information in a more persistent meaner. At least I got that part right.
Going with this approach I ended up creating a ViewModel for each Model type. If a model object had a collection of other model objects, so would the ViewModel. Only they would get mapped as well and exposed through an observable collection. Whenever a ViewModel changed, the backing Model would be updated. When it came to changes made to the collection property, that would update the Model as well. It sounds great. Every single time I mutated the ViewModel I had the backing Model in sync and ready to be stored. It didn’t matter if I was really going to do it, it was always ready. A true 1-to-1 mapping, one would ask why have the ViewModel and the Model separated in the first place. They look very similar to one another. The idealism at the time was that the repository would have to deal only with Model objects and not with ViewModels. A very important principle. At the same time the code should be DRY (Don’t Repeat Yourself) meaning that something was wrong. If the ViewModel is a 1-to-1 mapping to the Model then either there should be only one, the ViewModel in this case because I need observable instances to be able to use them in the UI, or the ViewModel should be something else than this mapping. In the latter case, it means that indeed the repository should only know about the Model, this is valid. While the ViewModel does know about the Model, it does not map to it in this sense. It consumes the information coming through the Model, but it does not map to it like this. The ViewModel should not be a precise 1-to-1 mapping because that would make the code WET (Write Everything Twice).
Alas, it took me a bit to move on to a different approach since this one was not exactly what the pattern is about, or at least what I think it should be. I continued with this approach a bit to write more generic code. At the time I was playing around the dynamic type introduced with C# 4. It was more about having “dynamic” properties on objects without having to generate code and be able to use the usual object notation. The only thing that it was missing was the IntelliSense because it had no idea what properties I decide to add to objects at runtime. Not a big issue since I was interested in having my ViewModels dynamically “generated” from Models without using a code generation tool. I believe you can see where I went with this. I created a core ViewModel type that would populate their properties from a Model object provided through the constructor. All properties were observable. Collection properties were observable collections. Any Model object that was aggregated was exposed as a ViewModel instance that wrapped the related instance. I even covered the circular reference issue (A references B which references C which points back to A, this is no longer an object tree, it’s a graph) with a reference cache so the dynamic ViewModels would point to each other in this case.
It worked great, the problem was that data binding does not work with dynamic types, if the property is not defined on the type then the binding fails. I am not sure about the relatively new x:Bind directive. It might work with that. I haven’t tried it as playing with the dynamic type was mostly over.
This approach would not work very well as I still needed to integrate commands into all of this. The base ViewModel class could work as it would do all the syncing with the Model objects and in derivatives I would only add commands that do something with the Model. It can get very complicated as there are different scenarios to cover and a generic approach does not really work out in the end. In my experience, still not a lot to this day, I’ve learned that “generic code” (as in reusable, really reusable, and not some bold claim) is emergent, trying to write reusable code right from the start can be very difficult in most cases. Sometimes it is obvious, but most of the time it is not. Instead of writing reusable code from the start, I write code specifically for what I need and go from there. If I can refactor and extract a few pieces that really are reusable then I do that and not the other way around. For me, it is easy to get lost in the idea that my code is reusable from the start without actually making it usable in the first place. I focus on making it usable first, and reusable afterward. It sounds obvious when worded like that.
Since the dynamic approach was not working, the only option left was to write ViewModels by hand for each case. This is still the case anyway, it might have gotten only more refined over time as I learned new things and applied them. This approach worked the best as I had specific code from which I extracted one or two base types to make my code more DRY. I had my Model objects with my ViewModel objects, observable instance, and all the good stuff. A bit of a mix between what was really observable and what was not as in the ViewModel was exposing observable collections that contained instances that were not observable. Not Model objects themselves, some other DTO that I used to map them to. Code was finally flowing a bit, I got to make some UI and got to interact with my application, this was still a pet project that I was developing in my free time. It turned out to be a learning project, but I enjoyed it nonetheless. I do like writing code.
It was great until I got to two-way binding, converters, and input fields from which I expected numbers but I could write anything as they were text inputs. Validation was the next big thing to address.
Inputs are one place where two-way binding becomes very useful and this is one of the mechanisms that came with XAML. This is very useful when dealing with forms as the property used to read data from is used to set input data that comes from the user.
Sometimes the value exposed by the ViewModel is not exactly what is needed, maybe we want to format these values or convert them to something else. For instance, boolean values can be shown as Yes/No values, dates can be formatted in a number of ways. Sometimes we need a converter to transform the value we read from the ViewModel to the value we display. When it comes to forms, we need the same converter to do the reverse operation, to transform the value entered by a user to the type of value the ViewModel exposes.
A very common example is numeric inputs. I want to expose a number field (e.g.: an amount of something) and edit it, there’s no number input field in WPF therefore we need to use the text one. Therein lies the problem. Converting numbers to strings is easy, but converting strings to numbers is not that easy. The user may decide to enter any kind of value in our input and may use different methods to bypass our ways of disallowing this. Not any string can be converted to a number, we would like to tell the user that we expect numbers or we may disallow non-numeric characters altogether. The issue is that the validation logic should be in the ViewModel, we would like to be able to test this with ease. The only thing that has to do with presentation is the list of errors that we display which in turn comes from the ViewModel.
For the value to get to the ViewModel it must go through the converter, we must transform a string to an int in order to get it to the ViewModel where validation can kick in. Well, how do we parse a non-numeric string to an int? Do we use nullable values? Does null mean invalid in this case? If this is true, then can our ViewModel load a null value from the database? We can make some sort of agreement that null is invalid and it cannot get to the database, but it gets a little ambiguous. What if we want to show a message such as “The value ‘ABC’ is not a valid number” to the user? How do we get the actual text value that is typed in the text field to the ViewModel if we tried parsing it and returned null from our converter? This can easily become problematic with two-way binding, it imposes some restrictions on how we write code depending on what the user needs, and picking one way of doing things imposes restrictions on how the user interacts with the application. Not a very solid argument. Applications are made to help users, not bring restrictions on how they can be used. If our architectural decisions bring limitations on what we can allow a user to do that would be intuitive for them, then that decision may not be a very good one.
Two-way binding and converters may not be the best pick as they may be more limiting than we would like, if we have a custom input that allows only numbers to be entered then we wouldn’t really need a numeric converter as the input will be doing that for us. Whenever the user enters a valid number the “Number” property (or “Value” property which is of a numeric type) gets updated. As long as the input is invalid we cannot submit the form, in this case, we need the custom input to tell us whether the entered value is valid or not otherwise we cannot block the submit from the ViewModel. If we cannot block it then the form gets submitted with the last valid numeric value, not at all what we intend. Forms certainly come with their set of problems. Two-way binding works best for them, however adding a converter may only complicate things.
An alternative is to block invalid characters from the custom numeric input, this works as we will always have valid values. The only validation rules we need to add are on range and number of decimals, we can configure the input field to allow this and add extra validation in the ViewModel to make sure we don’t mistakenly enter invalid values, just in case the user finds some way around it. We can even go on and have objects for numeric fields that have the range and numbers of decimals set on them and have the numeric input control bind to these values. We always handle validation at the field level, but we use this information at the control level as well to improve the user experience.
Finally, we can add all the validation at the ViewModel level leaving the input to be a usual text input. Whenever an invalid number is entered, we display an error message. All options are acceptable as they all depend and how the user wants to use the application, none of them are limiting in this regard.
This is a funny side of two-way binding and value converters, the best way to use converters is through one-way binding actually. In this case value converters can make a lot of sense when it comes to formatting values such as dates and numbers, we can have a converter that does exactly that. It takes an IFormattable instance and applies a format that is provided through the converter parameter, if we specify a converter culture we use that instead of the CurrentCulture. This enables maximum flexibility as we can make use of standard format strings, which are culture-sensitive, or use custom ones. If we really want to use the same culture-specific format string we can always specify it through the converter culture. This puts more focus on unidirectional data flow which is something that Flux puts a lot of emphasis on, something that makes the entire UI flow very clear.
Every application, or almost every application, that has a UI probably needs to concern itself with asynchronous operations, especially if they are consuming an API. Sometimes an API call is quick and sometimes it is slow, it depends. One thing we know for sure is that we do not want to keep the user with a frozen window while this operation completes. Generally, if the UI thread is too busy to execute one single callback (e.g.: a method waiting on an API call by blocking the UI thread) then the operating system will mark the application as Not Responding. Remember that? I bet you are happy when that happens.
This is not a new paradigm, asynchronous programming has been around for a very long time. What changed is that now we can use it a lot easier. Before, we had to use callbacks and call multiple methods just to get an async flow going. When we started the operation we passed a callback that gets executed once that operation completes and in that callback, we would call the method that gets the result which is a blocking call. We were doing it in the callback because in that case the operation would be completed and it wouldn’t block the thread. It already sounds complicated. What happens when we need to chain these operations? What happens when they are in a for loop? Clearly, we can write a lot of messy code that is hard to follow and debug with this approach.
This is one of the last challenges I encountered with MVVM, writing asynchronous code. This generally happens when we execute a command to load a list of items when we open the application. It is a bit annoying to open up the application and have to click on a button to load the list of items, why can’t the application do that automatically? Why do I have to click a button to see what I obviously want to see? In this case, we need to execute the command or tell the ViewModel to load the items when the page is loaded. This can be done through a command or simply expose a method that does that and call it from the Loaded event.
How do we let the user know that there is a background operation going on? There are a number of ways of doing this, the most simple one is using a flag. An “IsBusy” property will do, we can bind this property to different controls that get enabled or disabled or maybe even hide some of them. We can use converters to transform this flag to a visibility value or negate the value. It’s really simple and really powerful.
An alternative is to have the ViewModel go through different states, we can bind the state of the ViewModel to multiple visual states on the UI. This enables a lot of flexibility in terms of what we can do, sometimes it’s something simple and other times it is something complex, like an animation. Visual states are built for animations, they bring a lot of features, transitions, transition functions that generate different effects, and a lot of good stuff out of the box just for that. This was the thesis of my dissertation actually. There’s a lot of power in visual states and a lot of XAML to be written. If you don’t need animations don’t use them, you can bring them in specifically for different controls or pages if you need them. If you are okay with disabling elements or hiding them, showing a progress bar or progress ring while the page loads, use the “IsBusy” flag approach as it covers all of that. No need to complicate things just because “we may need it one day”, instead, make it easy to “complicate” things later on if you really need to.
What makes asynchronous operations a bit difficult to work with in MVVM is that the ICommand interface has no correspondent for asynchronous commands. All methods are synchronous, this is not a problem for the “CanExecute” method as we instantly want to know if we can execute a command or not, the “Execute” method, on the other hand, is in a lot of cases asynchronous, or at least it should be. We can have async void methods, this is not the issue, the issue is that we cannot properly test this without some workarounds simply because we have a void async method. We need to return a Task so we can await it in our tests. Otherwise, we need some hacks or some other way of finding out when the command has actually executed.
The fix is more or less simple, we can add an interface that extends the ICommand one so all of our command bindings will work, and provide an asynchronous version for the “Execute” method. We still have to implement both of them, but the synchronous one is based on the asynchronous one. Can get a bit repetitive.
To solve this, we can have an abstract class that implements ICommand and exposes asynchronous versions of the “Execute” method as abstract, this will reduce the repetitive code. Having a “true” asynchronous method, well, at least one that returns a Task that we can await, will save us a lot of trouble when we write unit tests for our ViewModels.
Something to keep in mind, when we execute asynchronous operations through a ViewModel we should consider that the ViewModel itself is consumed by the UI and we may block the execution of other commands. We can still execute commands by calling the “Execute” method directly, the UI will always use the value returned by “CanExecute” to determine whether a button is enabled or disabled. We can do an extra check before actually executing the command by calling “CanExecute” in the “Execute” method and throwing an exception if the result is false. This is an extra check that may not always make sense, it depends on the application and how sure we really want to be when we execute a command.
A subtle but important aspect when firing asynchronous operations for loading data, operations that do not get triggered when a user clicks a button, is to ensure they start on the UI thread. If you are starting them in the constructor you may end up in situations where the controls are created on a different thread, this means that the ViewModel will be created on that thread as well, and after they are mounted to the visual tree all the life cycle events get fired from the UI thread. If you are starting an asynchronous operation on a different thread than the UI one, then all update notifications for bound properties will be executed on a different thread which will lead to an invalid operation exception. You can only update UI controls from the UI thread. This is most easy to reproduce by having a ViewModel as an App level resource (defined in App.xaml), in the ViewModel constructor call an async void method that has a 3-second delay and then updates a property. On the main page, bind a control to that property. The reason it crashes is because at the moment when the App is initialized, there is no UI thread. The best way to tackle this is to use the life cycle events to call commands or methods that load data. This will ensure that the asynchronous operation started on the UI thread and can have its continuations executed on that thread.
I started to learn React, I did the to-do list tutorial to get accustomed to its concepts. What better way to learn a library beyond a tutorial? I wrote my own app of course! An app that I have implemented a few times in a different language. I first did that in high school with C++ and DarkGDK (back when I was exploring game development, well, sort of), then I did it again in C++ but this time with SFML. I implemented the game of Tetris. I did the same in React, as expected the Model got a bit messy, and one components’ state got a bit all over the place. The next step in learning React and about SPAs, in general, was Flux. A front-end architecture model that I did not encounter before, one that would address a lot of the problems I had in the past and, without knowing, it introduced a few new ones that we didn’t have before.
Flux: A New Way of Structuring the Front-end
Most likely, you already know about Flux, if not go watch Facebook’s presentation on this, they are the ones that introduced Flux. They highlight the issues they’ve encountered with MVC, and possibly even MVVM can have these problems, it’s a lot of good information there.
The core Flux components are:
- Actions: data transfer objects that represent an action
- Dispatcher: a central message bus
- Stores: where data lives
- Views: where data is displayed
The core Flux concepts are:
- Unidirectional Data Flow: Views create Actions that get Dispatched to Stores that interpret them and may update themselves.
- Single Source of Truth: no data redundancy, data is stored only in Stores, and the same data may not be found in multiple Stores.
- No Cascading Updates: a Store may not Dispatch Actions while it updates, and a View may not Dispatch Actions when it receives Store updates.
If you look at how the components integrate with one another you can see that this is event-based. Actions can be event objects that get to the Dispatcher, an event bus, which notifies all subscribers, and the Stores. As stores update, they emit events for notifying views that something has changed. It’s all events under different names to give them context.
Stores are singletons and get registered to the Dispatcher, which is a singleton as well when the application starts. It is possible to have a more dynamic solution in which Stores may be created and registered later on when a certain page is loaded or based on some other logic. We can have a lazy instantiation of stores. The main reason to avoid this is that all Stores should receive all dispatched Actions so they are able to handle the ones that are relevant. It’s entirely decoupled from the architectural point of view.
Views consume data from Stores, even multiple Stores if that is the case. When they are mounted they subscribe to events published by Stores and when they are unmounted they unsubscribe from them. Through these events, the view is able to update itself and reflect the changes in the data exposed by Stores. Most if not all SPA libraries or frameworks have component life cycle hooks to do various tasks such as these.
Actions typically expose a type property which uniquely identifies the type of action that was taken. Think of it as a type ID. This is important for Stores as they will primarily look for the Action type to determine whether they should handle them or not. An alternative would be to use classes and check if an Action is an instance of a particular class, this enables inheritance.
Action dispatches are done by components, not necessarily when a user clicks a button. This can happen when we navigate to a new page and the component gets mounted. Besides registering to Store changes we can dispatch a few actions for loading data that we want to display. We can dispatch actions when the user types in an input or clicks a button. We can hook to the router and dispatch Actions as the user navigates through the application.
This works really well because we can keep adding Actions for different parts of the application without having to change existing ones or existing Stores. We keep on adding for each feature we implement. Flux scales in this regard, not to mention that there will be few to no merge conflicts when more people are doing front-end work because most of the time they will be adding new files rather than changing existing ones.
One of the more popular frameworks that implement Flux is Redux, something you most likely heard about and most likely even used. An alternative to Redux is MobX. There probably are other libraries or frameworks out there that implement this architectural pattern to help get started faster so we don’t have to implement everything ourselves. The same architectural pattern is implemented by VueX for VueJS applications.
As with MVVM, we come across the same challenges because they are front-end development challenges actually. We have them regardless of the architectural pattern we use. They usually are forms, form validation (from simple to complex), and async operations (generally API requests). There are other challenges such as rendering large lists which is usually done through virtualization, but this is something that has more to do with UI components than front-end architectural models.
Forms are probably one of the biggest challenges when it comes to UI work because it has to do with what the user enters. While we can have more simple scenarios in which we only have to validate the value of one input, e.g.: to be between a maximum and minimum numeric range or date range or that the text does not exceed a certain limit, we can have more complex scenarios where the value in one input determines the possible values of another input. To make it even more complex, it is possible that the two inputs are codependent and both bring restrictions upon one another. This usually happens with field pairs that represent the state of an entity, such as the stage and status they are in. We can have multiple stages and each stage has its own statuses, some statuses are common. When we select a stage we only have a subset of valid statuses and the same applies in reverse, when we select a status only a subset of stages are valid.
For these scenarios we need to have access to the entire form, we can even get to the case where filling one input may update other inputs with default values. In order to do this easily we need to go through the Flux flow, this means we need to dispatch Actions when inputs change, trigger a callback through which we can handle form validation, and set other fields as well, everything goes through a Store which maintains the form state. Eventually, the View is notified about the changes. Errors get updated and potentially other fields as well. This goes well with Flux because we can already see how this can be done in a decoupled fashion. The input dispatches an action when the user enters some text, that action triggers a callback to which it passes the current form state (including what the user typed in the input) and the input that was interacted with. In this callback we can perform validation, this is no different than any other function or method, we can have reusable bits of code that handle common validation scenarios (e.g.: range checks) and also set different fields. We can even configure specific callbacks for each field, based on the input that was changed (we can use its name) we can identify a field-specific callback from a map or dictionary and invoke that as well. This will help us keep our code tidy and easy to follow.
This is only a general pattern that should cover most cases, for libraries or frameworks that implement Flux there already are other libraries built on top of them that handle all of this. For instance, for Redux we have Redux-Form.
The other challenge is asynchronous programming. Typically this revolves around API calls as internet connection and back-end processing are involved, we generally want to let the user know that something is going on in the background and that their request is being processed. We can display a loading bar or a spinner while the API call is underway. This is generally done by dispatching multiple actions, one that tells the Store that an asynchronous job has started, when the operation completes a second action is dispatched telling the Store that the operation has been completed and eventually providing the result so the user can see the data. This is similar to how we handle form changes in the sense that there are multiple Action dispatches, the difference here is that it has less to do with user input, and generally little to no validation is needed. We are simply performing an async operation.
Both validation and asynchronous operations follow a common pattern known as Action Creators. Instead of having the View dispatch an Action directly, the View calls a function or method that performs one or more Action dispatches behind the scenes. This can also be applied to simple scenarios where we are only dispatching one Action, for consistency only.
Basically, we expose objects or functions that handle the logic we need, form validation, API calls, computations, and so on. The object itself, or functions, encapsulate the Dispatcher, and depending on what they are doing they will be dispatching one Action or another so that Stores receive them and update accordingly.
One constant when it comes to choices, they all come at a cost. While I was hyper-enthusiastic about Flux and how it solves so many problems, I didn’t know about the ones I’m letting in. It was not clear to me what the trade-off is for using this approach, but I learned it by using it. A lot of what I know I learned this way and it’s probably one of the best ways to learn or gain insight. I am grateful for that.
One of the most common problems that come with Flux is stale data. Since our Stores are singletons and hold all of our data in the application (as we progressively load it of course) there is one slight risk of leaving this data hanging around when we no longer need it. This does not happen in MVVM because the life of a ViewModel instance is limited to the life of the View that uses it. Every time we navigate to that same page, a new ViewModel is created. This is not the case with Flux, every time we navigate to that same page, the same exact Store is used to display data from.
Stale data will bite you in the rear if you do not pay attention to it. For instance, moving all data to Stores can make some modals that you opened in one page randomly just show up when you navigate to some other page. Stale data can cause a heap of problems if you forget to manually clean your Stores. An alternative is to reset the state when you navigate to the page, which is a bit odd. You would have to think in terms like ‘I don’t know who was here before and what they were doing, I better clean this up before I use it’. The focus is moved to ensuring you have the right state rather than just loading the data you are interested in because some other component may have set some flags in there that should be rather unset. We don’t really expect this when we navigate to a page, we would rather always start from a default state.
The next one is a bit of a contradiction, it depends on the point of view. Flux does not scale. You read it right, it does not scale. While in the presentation video, you may hear this claim, it only scales in the sense that you can add more Stores, more Views, and more Actions having little or no impact on other developers. This is true. What they don’t tell you is that each Store, every single one, is registered with the Dispatcher. This means that every time you dispatch an Action, it gets to all Stores. If you worked with Flux, or any of its implementations such as Redux or MobX, then I have a question for you. How many times has an Action been handled by more than one Store (or Reducer in case of Redux)? How many times the first thing you do when you handle an Action is to check its type and if it is not what you are looking for you return from the handler? 99.99%? All the time? You can see where I’m going with this. Each time you dispatch an Action it goes through each if statement, every single one of them, and most of the time only one will pass. Add a new Store, that’s one extra if statement you do every time. Add a new Action, that’s one extra if statement that passes only once. This does not scale in this sense.
You may say that if you have 100 Stores this is not such big of a deal, 99 if statements that fail when the handler is invoked, the call itself is not that significant, it’s actually pretty quick. I agree, couple that with a library or framework that does a dispatch each time one of its components gets mounted (I’m looking at you, Redux-Form). If you have 10 inputs in a form with Redux-Form, these are 10 dispatches that happen when these components are mounted, one after the other they are stuck in a synchronous operation that executes them all. These are no longer 100 function calls with 99 if statements that fail and one that passes. These are 1,000 function calls with 990 if statements that fail and 10 that pass. What if we have 20 inputs? What if we have 50 inputs spread across a few tabs so the user can actually fill them? Flux does not scale here. The entire nature of how React works and that we use the component mounting and unmounting life cycle methods to dispatch Actions will ensure that once we load a tree of components, each of these life cycle methods gets called in one go. First on the parent, then on the children, then on the grandchildren. They all execute in one go and if that takes long enough it freezes the browser. In our application, if you go to the edit page of one of the big entities, Google Chrome freezes! It doesn’t even fully work on Internet Explorer! Some pages do, but the main ones, not so much.
To add to this, think about writing in these inputs now. On every key press, every single one, we have at least one dispatch that goes through 100 Stores just to update one string. We must have made a wrong turn somewhere. Flux is not meant for this sort of thing, if it is then it’s a really bad pick for large applications. Small to medium, it works. When we don’t have forms that are that complex and we don’t have that many Stores. Once our application becomes large, problems will follow just because of the architectural model.
This is a small lesson I’ve learned when developing Single Page Applications. Dev test them on Internet Explorer, you can do all of your debugging in whichever browser you like or attach a debugger, whatever floats your boat. When you test your app, do it in Internet Explorer. It is so slow you will see performance problems a mile away and you can still do something about them. All the code is fresh and can be made to run okay on IE. If it runs okay on IE, it runs great on any other browser.
Without a doubt, Flux brings good principles to abide by when it comes to front-end architectures. Single source of truth, unidirectional data flow, and no cascading updates. These are very important. We only need to find a way to apply them and somehow solve some of the problems that the architecture model came with.
During development, you sometimes notice some issues that you did not encounter while using a different architectural model or pattern. If you take a closer look you can identify the differences and see where the trade-offs are which in turn will make you a better developer because you better understand what picking one pattern over another means.
Some of these issues do not exist with MVVM, there’s no stale data in MVVM and it scales on both dimensions. We can keep adding ViewModels as much as we want, we can instantiate them as much as we need and there’s no global Dispatcher where all of them are registered. In most cases the View deals with one Store, if we bind the View directly to a ViewModel that it instantiates (or we get an instance through Dependency Injection or some other creational pattern) then we no longer need to dispatch Actions that go to every single Store or ViewModel. We no longer have that problem and we can expect this to work fast as we can customize the ViewModel to expose all the properties our View needs. We can have the form definition defined by the ViewModel, at least the structure of it alongside the validation rules which were part of the Action Creators. The View interacts with just one ViewModel. In most cases with Flux, the View dispatches Actions that are dedicated to only one Store.
MVVM can certainly simplify this, especially through its Two-Way binding. This goes a bit against the unidirectional data flow idea, but it makes a lot of sense when it comes to forms.
If you think about it, we actually have two flows that we need to treat independently. One is the read-only flow where we load data and display it, MVVM or Flux do this really well. There aren’t a lot of dispatches to worry about performance, either pick is just as good.
The other flow is the read-write flow where we do not only load data that the user can see, we allow the user to edit it. This is where it gets complicated because whatever the user enters, we need to validate it. We need to ensure that whatever they typed is something the system understands. Going through the entire Flux flow for each key press is unnecessary, it’s a lot of code that runs just to update one string and most of it will hit if statements that return from the handler (quick exist), unnecessary function calls as well. This is where Two-Way binding shines. The same property that is used to read data from we also set data to. When we work with forms we always dispatch Actions that are handled by one Store. What if instead of a Store we used a ViewModel? We would be able to have Two-Way binding, whenever the user edits an input we set the respective property directly, no dispatches, no 99 if statements that fail. When we set the property we can also validate it, it adds some extra code that gets executed once we set the property, but it is the validation rule for just that one property. We can tie a few other rules as well, like checking whether some other property is set or not, but these are the most complex scenarios we will generally have.
When it comes to forms, MVVM seems to be a much better fit because we need a two-way street to make it easy when a user edits inputs. We have the property we bind to, we know its type and we know its associated errors. We can even go one extra step and expose Field objects from our ViewModel, each Field has a Value property, to which we bind to and update, and an Errors property which contains error messages for that field.
Going Back to MVVM? No More Flux?
Every pattern has trade-offs especially when they are more loosely defined which allows for more interpretation. MVVM has its own set of problems just as Flux does, we need to instantiate a ViewModel each time we use a View, to do this we need to resolve its dependencies which come with its own set of challenges.
The issue I had the most problems with was actually designing ViewModels. It wasn’t until I started using Flux and Redux that I began to understand a better way of designing them.
We need to bring the best of both worlds to improve upon the design and ultimately have a better approach toward front-end architectures.
In most cases we don’t need the entire Flux flow, we can combine the Action Creators that dispatch dedicated Actions to one Store and the Store itself into a ViewModel. When we need to expose an operation, we do that through an ICommand. We can expose data through properties, if we are on the read-only flow then each property should be read-only and writable only from within the ViewModel. This will ensure that nobody else can edit these properties which enforces the unidirectional data flow principle.
When we are on the read-write flow, this most likely means forms, we can have a specific base ViewModel and a few helper types defined to make form definition really easy. This is quite a specific case already which allows us to create helper types to reduce duplicate code, and increase clarity and consistency.
You can probably already see a lot of similarities between ViewModels and Stores combined with Action Creators. This is exactly where I am aiming at, we can design our ViewModels more like we design Stores. Only one active instance of a ViewModel may exist at a time, this enforces the single source of truth principle. No View should bind to a different ViewModel instance of the same type if one is already in use. If no other View is using that instance anymore then a brand-new one can be created. This will avoid the stale data issue. To ensure this we can do some Dependency Injection voodoo or we can just request the ViewModel through the props of each component that needs it. We can design our Views so that one of them instantiates the ViewModel every time it is mounted and pass it down to other dependent components.
ViewModels should be unit testable. To make this easy, all of their dependencies should be provided through the constructor so we they are explicit and we know specifically what to mock. We can use a dependency injection container to create instances of ViewModels or use a default constructor which calls the parameterized one by resolving its dependencies through a Service Locator (or plain module imports).
Each Action Creator maps to an ICommand exposed by the ViewModel, whenever we want to signal a button click or something that the user did, we execute the command. Having commands that are bound to callbacks implemented by the ViewModel should help with readability as the callbacks themselves may be mutating state inside the ViewModel, this enforces encapsulation.
Learning how to design ViewModels is important as they are generally enough for most of the tasks we need. However, there are a few scenarios where we would like to have a global state and have it reachable from just about anywhere in our front end. An example that comes to mind is user information, once a user is logged in we would like to have the information relevant about them (ID, claims, roles, and so on) generally available as our ViewModels will most likely have logic based on that. Whenever a user logs in or out of the application, any active ViewModel would need to be notified about that and update their state accordingly.
This sounds a bit familiar, a global state that is updated based on events that happen in the application which notifies observers. That’s basically a Store from Flux, only that with MVVM we do not have a Store for every piece of data we have in the application.
In this sense, Stores are useful and solve this problem even in an application that is developed using MVVM. The difference is that in this case there are indeed multiple observers of the Store, there are multiple areas from where the Store may update (the user logs out, their session expires, and their login is no longer valid) and there is no stale data. The UserStore can expose a User property which when it is null then there is no user currently logged in, when it is not null then the object we get represents the currently logged-in user. Depending on the events the UserStore receives it may set the User property to null or to a value representing the user.
This introduces the issue of cascading notifications. If a ViewModel is dependent on a Store then whenever that Store updates the ViewModel may update as well which in turn may notify Views that something has changed. This does not sound that complicated, we register to the PropertyChanged event that Stores expose and whenever they update we will know about it. What about unsubscribing from that event? This is the tricky part, if we do not unsubscribe then the Store will maintain a strong reference to the ViewModel even when it is no longer used by any View.
In this case, we can chain event subscription. Whenever someone is interested in our ViewModel, we maintain a subscription to the Store. We can subscribe to the Store when we get a first subscriber to our ViewModel (whomever it may be). And we unsubscribe from the Store when the last observer unsubscribes from out ViewModel. Typically this would mean only Views, thus when there is no View using our ViewModel then there is no need for that ViewModel to be subscribed to any Store.
We need to be careful about this when we store data in our ViewModel that is, in part or entirely, based on data from a Store because the latter may change and the former may not be observed which in turn will make the ViewModel unaware about any updates in the Store. This can lead the ViewModel to have outdated data.
To solve this we can have a callback that is called on first ViewModel subscription. This will allow us to refresh our Store-bound data. or we could always compute the result of our operation that has Store-bound data instead of maintaining the result in the ViewModel. Whenever the Store changes, we simply notify the ViewModel observers that a related property may have changed which will trigger an update on the view. When the View first reads data from the related property it will get the latest value because it is always calculated and not stored.
Single Page Applications are a natural evolution of web-based applications. They are basically desktop applications in the browser.
Flux brings 3 good concepts that we should adhere to when implementing front-ends: unidirectional data flow, single source of truth, and no cascading updates.
The issue comes with the global state that is maintained in Stores which can lead to stale data alongside having each Store receive every Action that is Dispatched. There are enough function calls that happen and are unnecessary.
Model-View-ViewModel can solve this problem as we instantiate ViewModels each time we mount base components. There’s always a fresh default state waiting for us. Once the View is unmounted, the ViewModel can be reclaimed which reduces the memory footprint of the application in general.
We can, and maybe should, use MVVM as an alternative to Flux and libraries that implement it, like Redux and MobX. We can have a better overall design if we integrate what is best from the two. We need to design ViewModels more like Stores, following the three concepts on which Flux is based on with only one exception when it comes to unidirectional data flow, forms. Forms can use two-way data binding because it simplifies form definitions and processing of user input.
There are cases even in MVVM where Stores are useful. Having the currently logged-in user globally available can easily be done through a Store, which introduces event chaining and a layer of dependencies. Views depend upon ViewModels, ViewModels depend upon models and Stores. ViewModels observe Stores as long as they are observed themselves in order to avoid memory leaks (event chaining).
By Andrei Fangli
This article was originally published on Linkedin.