Decoupling your Application Logic from Angular4 with the SAM Pattern
The SAM Pattern (State-Action-Model) is a new Software Engineering pattern which can be used to decouple the business logic from Web Frameworks. Frameworks come and go and many of us have gone through the pain of having to migrate a large project from one framework to another.
A couple of years ago I discovered the SAM Pattern and since then, I have build a couple of large projects with it (one Angular2 and the other one with ES6) as well as a number of small ones. I am not sure how I could convince you to take a look at the pattern (I am clearly not a marketing guy), but all I can tell you is that every project I worked on has been a breeze (disclosure, the pattern is 100% open and free, I don’t have anything to sell).
One of the reasons the pattern works so well is that unlike other programming paradigms, SAM is based on the most robust Computer Science formalism, TLA+, and because of that, it corrects three approximations that we have all been using, pretty much since the first high level programming language was invented. The details about these approximations will be published in a separate post, but in a nutshell, I believe that when you correct these three approximations, the quality of your code goes up by orders of magnitude:
- Actions must not mutate state
- Assignments are not equivalent to mutations
- A programming step must be defined and implemented with precision
Most of the code I see violates these three principles. Event handlers rush to mutate application state, mutations are sprinkled throughout the code as assignments and there is no particular building block that we could identify as a programming step, any line of code will do.
The SAM Pattern is very easy to adopt: it’s a simple factoring of event handlers. All you have to do is factor the code in your event handlers in three buckets:
- actions which transform events into proposals to mutate the application state (but does not mutate it itself)
- a model which evaluates the proposal and accepts (or rejects it), it is the model which mutates the application state, as a single state tree
- a function that we will call the “state” function which computes the state representation from the model property values and makes it available to any component that need to access the “application state”
Not a single line of code can access the model property values (aka the application state). Components can only access a computed snapshot: the state representation.
An (action, model, state) loop is a programming step.
That’s it, we can actually summarized the pattern in one line of code:
stateRepresentation = State( Model.present( Action( event ))). then( nap )
nap is a function (the next-action-predicate) that computes if, given a state representation, there is any action that needs to be triggered, otherwise, when there are no actions, the event handler would wait for the next event (user or system event).
SAM is unapologetic about application state mutation, which is at the core of the pattern.
Let’s take a couple of examples to illustrate how SAM is implemented, one with ES6, in its simplest form and one with Angular.
A counter application would look like this:
Let’s now look at a more realistic example, using the SB Admin template (Angular4, Bootstrap4).
I am not a big fan of templates, but Angular makes it really easy to use a publish/subscribe programming model to pass the state representation to individual components. In this post, we’ll focus on the charts component.
The first step to “SAM enable” a component is to subscribe to a set of properties from the State Representation (see constructor). Each time a new state representation will be generated by the State function, if there are any relevant aspects to the “dashboard.charData” set, the component, any component that subscribe to that set will be notified and Angular will do it’s magic and perform the necessary changes to the UI.
The bind method “binds” the state representation properties to the component’s properties.
Now you need to wire the component’s events to SAM actions. Again that can’t be more simple, here is for instance how we wire the ngOnInit event to the corresponding initCharts action:
The State element is the only element of the pattern interacting with Angular components. Once an action is dispatched, it simply executes the corresponding SAM loop: action / model / state => component.
At that point 100% of your application logic resides in the SAM pattern!
Let’s look at how the SAM pattern is “wired in” Angular.
Step 1: provide a mount for the actions in index.hmtl
Step 2: Add the SAM providers (SamFactory and State) as well as any service your application logic may use
Step 3: add your actions, I generally use a simple “intent” mapping to connect events to actions, but that’s not mandatory
Step 4: Add model “acceptors”. Acceptors mutate the application state. I usually trigger an acceptor based on the content of the proposal. This creates a nice decoupling between the Action and the Model and enables a many-to-many relationship between actions and acceptors (i.e. a single actions could trigger multiple acceptors, and of course, multiple actions can trigger a the same acceptor).
Step 5: Once the model has mutated the application step, we are ready for the last step of the pattern, the State Representation. In this case, the state representation is rather simple, we extract the model.charts property value and pass it on the charts component. At that point Angular takes over and completes the rendering:
That’s it. The code comes with four classes you can reuse:
- SamFactory (which purpose is to create a SAM instance and wire the reactive loop between actions, model and state)
- Actions, Model and State
At that point every component is “business logic” free, it simply emit events and waits for a new set of properties!