Model in AngularJS

Ürgo Ringo
Wise Engineering
Published in
6 min readNov 4, 2015

--

AngularJS is one of these frameworks that is deceptively simple at first sight but once you start developing more complex apps you discover that there are quite many questions that don’t have a single obvious answer. I guess this can be said about any good framework as good framework leaves you with enough flexibility. However, flexibility also means you need to figure out some stuff on your own.

One of the questions we struggled quite soon after using Angular more heavily at TransferWise was:”what is the Model in Angular and how to implement it”? In following post I will show how we have approached this and what we have learned.

I have found it very helpful to take Angular as a Model-View-ViewModel (MVVM) framework. This gives a lot more guidance and context than thinking about it simply as something based on MVC or MV*.

Use Angular service or factory to make your Model explicit

As JavaScript (up to ES6) does not have any classes then how to make sure that same domain concepts have same form everywhere in the application. E.g if you have a userobject in one part of the app and user in some other part of the app then you want both of these to have the same set of properties and methods.

Even though Angular does not tell you how to implement your Model it still makes sense to use the construction tools provided by it. We started by using Angularfactories as you can return your constructor function from the factory and then create new instance using the new keyword like new User() where User is the Angularfactory.

Use factory method for creating Model objects to simplify testability

If Model object construction contains a lot of logic then instead of returning the constructor function from Angular factory it is better to use a dedicated “factory” method of an Angular service:

angular.module('tw.user').service('User' [function() {  
...
this.create = function(userData) {
//_ is from Underscore.js
return _(userData).pick('id', 'name', 'dateOfBirth');
};
}]);

The pick serves essentially as a dictionary so that you will know what kind of properties there are. It also enables to filter out the stuff that is returned from the server API but you don’t care about in your client. If we want to use the prototype then we can also do _(proto).create(_(userData).pick(…)).

Now we can create new user object like this: User.create({name: ‘John Doe’}). Compared to new User() this approach makes it easier to inject mock or stub User in tests as we can simply add a spy to create method of User (like spyOn(User, ‘create’)). Yes, we can spy also User Angular factory itself but that is a bit more hassle to set up in tests.

Mix server communication into Model objects to make application code easier to read while keepingSingle Responsibility Principle

Now that we have these Model objects then the next question is where should we put the server communication logic like getting and saving stuff. In the context of client app development I like the idea used by server side frameworks such as Grails GORM where DB access methods are injected into the Entities themselves. However, same way as in Grails where Entity class itself does (should) not implement all (HQL) queries, this Model should not itself contain the code of making the actual XHR calls. Instead this should be implemented somewhere else and then mixed into Model objects. So I can do stuff like User.create({name: ‘John Doe’}).save() or User.findById(id) where both save and findById are implemented by some separate AngularJS service used as a mixin to User.

There are some existing solutions like Angular $resource and Restangular which make methods for server communication available via Model objects. Depending on the design of server side API it might make sense to use one of these.

Value Objects that consist of multiple properties should be… well, Objects

When creating Model objects then we also need to figure out how we want to implement different Value Objects (e.g Currency, Address) that are associated with Entities. This is of course to some extent affected by the server API but even when API gives a response where address properties are mixed together with bunch of other user data then it is often worth the effort to model it correctly on client side. This will greatly simplify reusing formatting and other application logic related to that particular Value Object.

Such Value Objects can be implemented using Angular value. Of course we can also use factory or service but as Value Objects are typically quite simple and don’t require dependencies of their own then it makes sense to keep them light.

angular.module('tw.model').value('Address', function AddressValue(params) {  
return _(params).pick('countryCode', 'city', 'street', 'postCode');
});

Value Objects that are identified by a single property can be primitives (e.g String)

On server-side I would be inclined to create special class for each domain concept. However, on client side if we move formatting logic elsewhere then there typically isn’t much sense to model such things using anything else than Strings (or other primitives). Good examples are things like Currency or Country which both can be represented using their ISO codes.

Use “enums” or “constants” for values that are used in conditions

If we need to make branching logic based on some Model property then I prefer to avoid using literals. Having code like paymentMethod.type === ‘CARD’ isn’t very nice when you need to change type values. Much better alternative is to have something like this: paymentMethod.type === PaymentMethod.Type.CARD.

angular.module('tw.payment').factory('PaymentMethod', function PaymentMethodFactory() {  
function PaymentMethod() {
//construction logic
}
PaymentMethod.Type = {
CARD: 'card',
BANK_TRANFER: 'bank'
};
return PaymentMethod;
});

Use filters to display single property Value Objects

Displaying and correctly formatting these values can be relatively complex. Take for example showing amounts of different currencies (do we show decimals or not, how many decimal places, what symbol to use).

Adding display and formatting logic into such Value Object itself and hence implementing it using a JavaScript Object adds unnecessary complexity. Especially considering that such Value Objects can be quite widely used in a given domain. This would also make Model responsible for display logic. Hence better solution is to keep this logic in Angular filter and either pass the formatting configuration to it as an extra parameter or eagerly fetch all (e.g currency formatting) configuration on application startup.

Derivative properties like user fullName — in Model or filter?

If following pure MVVM then (data) Model should not contain any behavior. However, if the logic is quite simple then I would myself start by adding it to the Model itself and only move it to a filter once the code gets more complex like in the case of currency.

Extract state and behavior from Controller to special Model object if you need to share the Model

This is MVC at its best — using one Model for multiple Views. Lets say we have a currency exchange calculator where user enters source/target currency and amount and then system calculates amount in target currency. We want to show some additional explanation panel of how this calculation is done.

However, we don’t want to implement it as part of same Currency Exchange Calculator directive as it would make it too big and explanation is not always needed. Hence we create separate Explanations directive that needs to access the same Model. The solution is to move all logic from Exchange Calculator directive controller into a separate Angular service — Exchange Calculator Model which can be injected into Explanation directive’s controller.

Don’t treat your Controller as MVC Controller but instead as a View Model

In classical MVC the Controller has responsibilities that are not needed with Angular thanks to its two-way binding. Hence as Addy Osmani so well puts in his post it makes more sense to think of your View Model as a Model not Controller. This means that trying to move all stuff that looks like Model stuff into separate service makes little sense — if you do that then you just end up moving all code from one place to another without having any good strategy how to split complexity. There are better strategies to fight the “fat controller” problem but that is already different topic.

P.S. Interested to join us? We’re hiring. Check out our open Engineering roles.

--

--

Ürgo Ringo
Wise Engineering

In software engineering for 20+ years. Worked as IC/tech lead at Wise. Currently tech lead at Inbank. Interests: product engineering and org culture.