The Angular Model (@angular-extensions/model) or also previously called ngx-model
AngularInDepth is moving away from Medium. More recent articles are hosted on the new platform inDepth.dev. Thanks for being part of indepth movement!
⚠️ IMPORTANT — UPDATE 29. 9. 2018
The ngx-model
is officially deprecated, please use new library called @angular-extension/model
, it has the same functionality but embraces modern syntax and tooling!
- ✅ using new tree-shakeable syntax
providedIn: 'root'
- ✅ no more import in
@NgModule
- ✅ schematics are included in the same package
- ✅ supports
ng add
in Angular CLI projects
Getting started
- install with
ng add @angular-extensions/model
- generate services
ng g @angular-extensions/model:model path/my-model
- check documentation
In the beginning I was thinking about creating a blog post with a small demo on github but as time progressed demo turned into something which resembles full fledged docs website… (and there is of course a github repo available too) and new ngx-model github repo
State handling is one of the prime concerns when developing front end applications and over the last couple of years huge progress has been made by the open source community. One way data flow (originally named flux) became de facto standard way of handling state in most modern frontend libraries and frameworks.
This could be seen as a successor of original post from 2015 — Model pattern for Angular.js, updated for the landscape of 2017 with Angular 5, Typescript and RxJS.
Motivation
First of all I would like to express that all the libraries mentioned in this post are great pieces of open source software and served as an inspiration, source of ideas and comparisons when creating this article and examples.
State handling is one of the most important areas of application development. We should always spend considerable amount time before choosing particular state handling architecture so that it can accommodate for all of our expected business needs and use cases.
Obviously simple app with 3 forms and lots of real-time streamed data can probably benefit from different state organization than form heavy CRUD beast with hundreds of mostly static entry screens.
Redux (and many redux-like libraries), work with the concept of actions, which necessitates spelling out of ALL action types to accommodate for the possible mutations of complex application state object. This situation can get out of hands rather quickly…
As with everything, decoupling comes with the price… On the one hand, stuff becomes nicely defined and neatly organized. On the other hand you’ll find yourself searching for string constants to figure out what are the real and full consequences of emitting original action as it can be quite hard to understand the whole picture in larger code bases from the get go.
Actions can emit other actions which in turn emit another actions. This way, codebase can end up with circles in it’s action execution graph and debugging issues can become a nightmare.
Actions, dispatchers and others are by no way new concepts. I had the honor of experiencing one of the Java CQRS frameworks in 2014 and let me just say that the application stats module never worked as intended, but it took us about a year to notice.
Now compare that to the vanilla function calls and debugging them by stepping through the call stack one function call at the time…
Pattern vs Library
The whole concept is implemented using single short file following standard Angular service blueprint. This enables simple inclusion of the file in your project.
Including file instead of dependency has additional benefit of enabling easy customization based on your specific needs (eg: add logging or more complex middlewares…)
Also feel free to include tests which cover all the basic functionality which is provided by default.
On the other hand distribution as npm module ngx-model
provides standard developer experience when you just install the package and use it right away.
How to use Angular Model Pattern in your project
As mentioned above, model pattern is implemented using a single file. To start, simply include this file in your project, most preferably in your core module and register newly available model provider.
This enables you to inject ModelFactory
into your services and create model instances…
How to use model in your services
Import ModelFactory
in your service, inject it into constructor using Angular dependency injection mechanism and create a model instance.
After that you can expose model data as a variable with descriptive name (eg: this.todos$: Todo[] = this.model.data$
) to be consumed by the components which import the service. Don’t forget to provide model class or interface as a generic type of both Model
and ModelFactory
to get proper type checking and code completion support!
How to mutate state
Underlying observable clones data and makes our state is immutable by default. Because of that the only way to perform mutation is to explicitly call public service method in component or other service while passing all the required data as a function parameter.
Service then retrieves internal model state, mutates it and sets it back to model. Setting state automatically pushes new model state to all subscribed components and services through RxJS Observable. Model clones every new state update by default to ensure data immutability and to prevent accidental mutations by the components.
How to mutate state by events from different sources
User interaction with components isn’t necessarily the only source of state mutations in our applications
Let’s imagine an application which has to react to the events pushed from server over the websocket. Our approach will stay the same also in this case. Firstly we have to inject the model containing service into our websocket resource service. Then on every websocket push event we will simply call service method with all required parameter in the same fashion as we did in our component.
How to display state in components
To make our state useful we must be able to display it to our users. This can be achieved in two ways.
We can subscribe to our model data directly in component template using Angular’s own | async
pipe (which has an advantage of automatically unsubscribing when the component is destroyed).
Async pipe works the best with template structures like
*ngFor
or*ngIf
which enable us to store resolved result in some local variable so it is safe to access data without over reliance on safe navigation operator —(todosService.counts$ | async)?.done)
or need to subscribe (use async pipe) multiple times to access all of the model properties.
Sometimes we are better off subscribing to our model data explicitly in one of the component methods (most commonly ngOnInit
) by using .subscribe()
and storing data in one of the component variables. This enables us to use standard Angular template data binding again…
How to initialize state before route transition based on route params
Angular Router supports use of the resolve
property in the route configuration object. Specifying resolver delays route transition until the data was resolved successfully. In a typical use case component injects activated route to directly access resolved data stored on that route.
Angular Model Pattern uses resolve
property in a slightly different way in order to achieve model initialization before the route transition ends. Our service implements Resolver<boolean>
interface which means it only returns boolean flag (true
) to the route and uses resolved data to initialize its model instead. This leaves us with the flexibility to implement any recovery strategy in case of model initialization failure as part of that method. Throwing exception in resolve method will by default lead to aborting of active route transition.
Component then consumes service model as usual using this.todoService.todos$ | async
pipe in template or explicit subscription this.todoService.todos$.subscribe(/* ... */)
.
How to combine state from multiple services
It is expected that you will create more than one model in any non-trivial application. These models will most likely correspond to the business (domain) features of your application and infrastructure concerns (eg: session management).
Implementation of features which require access to the data of more than one model can simply be handled by employing built in RxJS operators like .combineLatest()
.
How to orchestrate dependent state mutation across multiple services
Sometimes, implementing business requirements may result in need to update multiple models and perform side-effects in well defined sequence. In that case it can be very useful to implement orchestration-only “application services” a term borrowed from DDD (domain driven design) literature.
Application services take care of invoking correct domain models and services, they mediate between Domain and Infrastructure…
Application services should contain only orchestration logic and don’t implement any business login on their own.
Comparison to popular state libraries like ngRx or Redux
Most of us are aware of great state management libraries like ngRx, Redux (with angular-redux) which provide nice standardized way of managing your state by defining concepts like actions, reducers, effects, dispatcher, store, selectors, …
Purpose of Angular Model Pattern it to provide same one way data flow as above mentioned libraries while reducing verbosity and abstraction overhead.
Actions
Instead of emitting action events on dispatcher, we explicitly call service method with all necessary parameters to perform model mutation. This enables standard debugging experience (stepping through call stack one function call at a time).
Reducers
& Effects
Service method implements all logic which is necessary to mutate model state. Side effects (both sync and async) can be delegated to other services and higher level orchestration can be implemented using application services when necessary.
Dispatcher
We are using explicit function calls so there is no need for dispatcher to emit action events with payload.
Store
& Selectors
Model exposes data as RxJS Observable through .data$
property. Service then re-exposes model data with descriptively named property (eg: this.todo$ = this.model.data$
) which is then consumed by the components and other services.
You can simply provide derived data too (eg: this.todosCount$ = this.model.data$.map(todos => todos.length)
)
Use cases which are better fit for ngRx, Redux, …
Many thanks to Greg Lockwood for great overview of use cases which are better fit for the more complex libraries.
One thing to keep in mind when evaluating whether to go with this, or full-blown Redux / @ngrx
is whether you currently, or will in the future, need some of the other functionality that centralised state management provides.
For example:
- easier logging via middleware
- local persistence of state that survives page reloads
- server side rendering, where you can send down the correct initial state to use
- easy to reproduce bugs if you have the sequence of actions across the whole app and a copy of the initial state
- time travel debugging, especially using tools like the Redux Devtools.
Obviously, these benefits come at the cost of adding boilerplate and some indirection and hindrance to productivity. This library is a great compromise for when you want the key benefits of uni-directional data flow like immutability, being able to use ChangeDetectionStrategy.OnPush
, and easier debugging.
That’s it for today!
Check out the documentation website and please support this article with your 👏 👏 👏 to spread it to a wider audience!
Also feel free to check out other interesting front-end related posts like…
Follow me on Twitter to get notified about the newest blog posts and useful front-end stuff.
And never forget, future is bright