Transis aims to fill in the gap that frameworks like React and Angular leave with regard to your business data. It provides structure around defining the shape of your model objects, their state, and the relationships between them. It further provides a powerful property observation system that makes it simple to keep your views in sync with your models.
Here is a high-level list of features that Transis provides:
- Typed attributes
- Bi-directional associations
- Nested data loading
- Model state management
- Model validations
- Attribute change tracking with undo functionality
- Property observation of both simple and computed properties
- Array observation
- Computed property caching
- React integration
We’ll take a quick look at some of the features here, but be sure to review the README for a deeper discussion.
Let’s take a look at how to define a Transis model. Imagine we are building an application to catalog our book collection.
Here we’ve defined two model classes:
Book. Each has some
which are special typed properties. The
attrs define the schema of the data you
receive from your backend. Transis ensures that the values are of the correct
type, coercing them if necessary. For example, if you set the author’s
attribute to the string
"1975-03-01", Transis will automatically parse it and
turn it into a
In addition to our model’s attributes, we’ve also defined some associations. The
Author model has a has-many association with the
Books they’ve authored and the
Book has a has-one association to its
Author. We’ve also named the inverse on
each association—this is what gives us the bi-directional behavior. This means
that when you update one side of the association, the other side is
automatically kept in sync:
In addition to the attributes and associations, our
Author model also has
something called a
prop defined. A prop can be a simple getter/setter, or it
can be a complex calculation of other props (an
attr is just a special prop).
When defining a computed prop that depends on other props you must declare the
dependencies using the
In our example the
fullName prop depends on the
props. When the
fullName prop is accessed, Transis automatically gathers the
dependencies and passes them into the getter function. So you access your
hood, the getter function that the prop was defined with is called.
Declaring your dependencies like this may seem like a lot of work to define a simple computed property, but it comes with some benefits. The first of which is that this property can be observed:
The second benefit is that the property can be cached by specifying the
true option. This is very useful when you have computed properties that are
expensive to compute. When a prop is cached its getter function will only be run
once no matter how many times it is accessed. Transis automatically clears the
cached value when any of the dependencies change so that it will be re-computed
the next time it is accessed.
We use React to implement our view layer and Transis was designed to work well with it. There is no dependency on React however—you should be able to use it with any view framework. The property observation in particular is what makes Transis so easy to use with React, because it is dead simple to keep your views in sync with your model.
A React component automatically re-renders when its state or props change, but
often you may find yourself needing to trigger a re-render when some deeper
manipulation is made to an object passed in via props. If the prop is an
instance of a Transis model, then all we need to do is observe the dependent
props and call forceUpdate on the component whenever they change. This can
easily be done with
This component will now re-render when the given author’s
lastName props are changed or the number of books in its
books association is
changed. Yes, you can observe changes made to associated objects, as well!
So far we’ve seen how to instantiate new models, but haven’t reviewed persisting them to permanent storage. Transis is completely agnostic about the persistence mechanism used in your application because it uses the data mapper pattern to communicate with it.
The data mapper pattern is simple—each
Transis.Model subclass that needs to be
persisted must have its
mapper property assigned to an object that responds to
one or more of the following methods:
These methods are responsible for doing the actual communication with the
persistence layer and are invoked by Transis when you call the appropriate
method on the model class or instance (such as
Since communicating with your persistence layer will likely involve an asynchronous operation, these methods must return a Promise or promise-like object that gets resolved/rejected with the response data when the asynchronous operation is complete. Transis will automatically load the response data into the model instance for you.
This post has presented a high-level overview of what Transis is capable of, but there is so much more that wasn’t covered here. Follow the links to learn more about what Transis can do, like: