This post is a continuation of AngularJS: a retrospective
We were tasked with creating a web application that was being developed simultaneously with an API from another team. Our AngularJS thick client web application had to be able to respond quickly to changes in business requirements, and changes in the API. Our priority of separating concerns and keeping single sources of truth served us well. We started by defining our own base data model class, which we called PropertyModel.
PropertyModel, our base data model class
Angular works with vanilla JavaScript objects, but plain objects can't introspect.
We wanted a standard interface for accessing both properties (object.property
) and methods (object.method()
). When we consume data, we don't want to worry about it's implementation. What we came up with is the PropertyModel. With a consistent interface, we didn't have to hunt down which parts of code requested a value that was once a simple property accessor is now a full blown method.
PropertyModel stores key value pairs on @properties
, and accesses them with get
/set
methods.
Because we're accessing via a method, we can redirect calls with an appropriate method in @accessors
prefixed with either get_
or set_
.
It doesn't matter if it's a plain old property or a method; it will be accessed as `model.get('propName').
User model with accessor methods
get_fullName
is an example composite property. There is no fullName
on @properties
.
get_gender
is an example of intercepting a value lookup. Imagine that @properties.gender
is normally present, but for some of our earlier users we forgot to ask for it. Here, we can make an educated guess based on @properties.title
.
get_isTaxable
is an example logic check for a business requirement. Perhaps your app needs to apply sales tax based on where the user lives. By having this check on the user model, when your requirements change, you can update the single source of truth. Note: Server side validation of user data is always necessary.
API service
Our services were responsible for all API calls. After a successful API call, a service creates a new user with the appropriate factory. For a failed call, the service logs the error to the console, then carries on the promise
rejection with $q.reject
without which the callee would not receive the error. When the API is updated the service may be the only part of your application that needs updating. How about that!
Retrieving the user from a controller
The controller only calls the service with the user ID it wants.