Applying a MVVM-like pattern in Angular (Part 2)

Zsolt Csik
6 min readNov 22, 2020

In Part 1 of our guide, we created a simple component based on the MVVM pattern. We created a view-model, a ui-service, some abstract classes and we bound them all together. From our html we bind to the values from the view-model and in the ui-service we manipulate it’s values based on some predefined logic. Finally, in order to forward some events from our html to the ui-service, we go through the component using simple method calls.

All of this already gives us a lot of flexibility and options but let’s see how we can take it further. In this part, we will tackle 2 main points:

  • listening for value changes in the view-model
  • injecting different ui-services based on some criteria

Let’s jump in…

Listening for value changes in our view model

Whenever we use some kind of form or data manipulation in our components, we find ourselves tracking when a certain value has changed. And not only from our UI, but events sent from a server can also trigger some kind of logic. We want to do action X when value Y changes. This can be applied to our view-model. But we need to make some changes in order to make this work properly and to minimize the amount of code we need to write.

First, we need to change our view-model definition. Instead of using an interface, what if we use a class, and we expose some even emitters for each field ? Change the code for the view-model as follows:

Note that you can use other approaches here like exposing a single event emitter for all the fields and pass also the field name as a parameter, or even expose just a modelChanged emitter. It’s your call. But I find that this approach (with one EventEmitter per field) is the cleanest and offers the most advantages.

If you run your application now, it should still work, but we are yet to leverage the power of this approach. Let’s make a couple of more changes.

  • First, remove the cutWings and makeHimFly methods from the component class and also from the ui-service.
  • Next, change the html code so the canFly value is set directly:
  • Finally, let’s try and listen for the change event in our ui-service and print the changes in the console, just so we see it in action. Change the ngOnInit method, from our ui-service, to the following:

But, trying to run it now, will most likely give you an error stating that canFlyChanged is undefined. This is because the view-model is still being instantiated as an empty object instead of using the new operator. Remember that this has now become a class instead of an interface. The problem, resides in the constructor of the abstract ui-service class:

We cannot directly call new as VIEW_MODEL is just a type parameter here, a placeholder. And we also want to think about flexibility, about having multiple ui-services use the same view-model. How would that go? Well, we can simply pass it in the constructor:

Change the constructor method of the abstract UIService to the following:

This will allow us to pass a view-model of our choosing, or to allow the ui-service to create its own instance as it did until now.

Now all that remains is to pass our HeroViewModel instance, created with the new operator, to super. So, in HeroUIService just add the following constructor:

Now if you run the app, everything should be working. You should see the text & button change on the view as it did before and in the console you should see the value changes being logged. You can subscribe & unsubscribe to each field change on your view-model whenever you need to.

A classic scenario, where we need to load the values in a dropdown, based on the selected value of a dropdown above, comes to mind. This pattern comes in handy in this scenario. Just listen to the value change on the first field, and load the right list of values in the second dropdown, all done on the view-model, all in the ui-service. And it’s easily testable.

Injecting different ui-services

Another often-appearing scenario that I came across is what I call “the same, but different” logic. Suppose we have an app that we’ve worked on for quite some time and we start getting clients. But each client comes with their own requirements. They want to change only a couple of things on each component. You can almost reuse the component, but not quite. It may be just a couple of extra fields or extra validations. Or sometimes the functionality is totally different on the business layer. Adding if-else statements based on some client-id, in your ui-service is ugly and can quickly become unfollowable and unmaintainable.

This is where this pattern & Angular’s dependency injection came in handy. I was able to create different ui-services for each client and simply inject the one I need based on some kind of client id. Moreover, I was able to exclude the files I don’t need during build time depending on the client I was building the app for. But that’s another topic. For now, let’s focus on creating another ui-service for our component and inject one or the other based on some flag in the environment file.

First thing we need to do is to think about how we could integrate 2 or more ui-services in the app and use them in the component. Right now, we don’t have any calls to our ui-service from the component class, but what if we did? The right thing to do is to create some kind of common interface that all of the possible ui-services should implement.

So go ahead and create a new file in our hero-component folder called hero.comp.ui-service.intf.ts with the following contents:

Next, let’s create the secondary ui-service for this component. Create a new file in our component folder called: hero.comp.batman.ui-service.ts with the following contents:

Also, we need to make some changes to our initial ui-service just so it’s in tune with the new approach. So rename hero.comp.ui-service.ts to hero.comp.superman.ui-service.ts and update with the following contents:

And of course, let’s not forget about our test files. Rename hero.comp.ui-service.spec.ts to hero.comp.superman.ui-service.spec.ts. And also create a hero.comp.batman.ui-service.spec.ts file. You can fill in the contents.

I’m a bit of a OCD freak when it comes to organizing my project files so let’s move the ui-service & ui-service.spec files grouped into separate folders (Remember to update your imports if VSCode doesn’t do this for you).

You should end up with a project structure that looks something like this:

Final file structure

What we did up to this point is simply created 2 different ui-services for our component, with the accompanying spec files, and group them under subfolders. They both implement the same interface and extend the same ui-service abstract class based on the same view-model definition.

All that remains now is to use Angular’s injection mechanism to inject one or the other based on some value set in the environment.

So first, let’s add that value. Open environment.ts and add the following:

superhero: 'Batman'

Now, we need to prepare the right structures for the injection. So open hero.comp.ui-service.intf.ts (although you can put this in some other file as well) and add the following:

And finally, in our hero.comp.ts, in the providers list, replace the SupermanHeroUiService with the following:

And the constructor becomes:

And that is it. Now, you can change the value of the ‘superhero’ field in your environment (between Superman and Batman) and you should see that the appropriate ui-service will be used. You can then set up multiple build configurations with multiple environment files and ship different versions of your app depending on your client.

Conclusions

I hope that you have found this guide useful and will make good use of it as I have. I know it may seem overcomplicated in case of simple components and you are right. I do not recommend using this pattern if your component is small and simple. However, if you foresee your components growing & getting complicated in the future this pattern is here “to the rescue”. I wrote this guide based on my own experience with it, applying it on multiple enterprise-sized applications. It may take you or your team some time to get the hang of it but you should definitely see it’s upside once you master it.

You can find the complete source code for this example on this GitHub page: https://github.com/Zet89/hero-mvvm

--

--

Zsolt Csik
Zsolt Csik

No responses yet