AngularJS is an opinionated web framework, so it guides users in doing things in a particular way. This is helpful when you have a project with multiple developers and especially when you start adding more developers in the course of the project, since it is quite easy for new team members to get into the code. So far the theory. In reality it is not enough to only rely on the Angular way of doing things, but to create guidelines about code structure, code style and naming conventions. If you don't want to create your own version but want to use a battle seasoned styleguide, I can recommend the John Papa styleguide which I stumbled upon in a lot of different projects.
How to structure code
Once a styleguide is set and enforced (e.g. by the use of ESLint) and the build system is set up (I still love Gulp.js even though everything seems to move into the direction of Webpack) we should start thinking about how to structure our code. AngularJS promotes the usage of Components (formerly Directives) to break down the UI into reusable parts and Providers respectively Services to provide functionality to those components. So far so good. Now, what happens if the user interacts with some component that triggers a state change in a totally different component? Do we throw an event that the other component listens to? Do we store all application data in a service and take advantage of two-way data binding to reflect data changes in the UI? Or (it hurts only thinking about it) do we bind all data to the
Don't mess with the Angular
As you can imagine, all of the above-mentioned techniques have severe drawbacks.
- Concurrency issues. If a change triggers an event leading to a change in some data, that could itself trigger other events leading to more changes and to even more events. With two-way data binding set into place we can make sure, that any user behaviour will result in the biggest Pandora's box you have ever seen. The outcome is neither predictable nor is it possible to write tests for it.
- Little to no transparency. For a developer it's almost impossible to understand what happens. Bug fixing will lead to suicidal tendencies in virtually no time. Having multiple developers in a project will worsen the situation. A properly documented event system might add some value in specific cases, but more often than not it will only hide the dependencies in your software and drive the developers nuts.
- Inadequate interface definitions. We should always try to keep the interfaces of our components clean. It does not only help the developer in writing tests but also to easily identify dependencies between compontents. When you start creating invisible dependencies by throwing events that have effects on other parts of the application you only circumvent the clean interface definitions that you created thereby rendering them useless instantly.
But I would not write an article on that topic, if I did not have a solution. Let me introduce my new best friend.
The lovely reactive pattern
In one of my last projects I had the pleasure to work with Florian Grandel who is an enthusiastic advocate of the reactive pattern. The principle is easy to grasp: the whole data flow thru the application is uni-directional. Every component has a well-defined interface declaring all data that is going into the component and all callbacks that will be called when something happens in the component. When binding data to a component it will be semantically transformed to make it fit to the context of the respective component. Sounds complicated but is absolutely obvious as you will see in the upcoming example.
Example: The product list
Let's take an example that can be found on almost any commercial website. We have a list of products, some filters to narrow down that list and a paging component to go thru all items of the list.
If we analyse that relatively simple screen, we could identify three different components: the list component, the filter component and the paging component. We can pack all those components into one parent component (the product overview). Let's write the product overview template to place our child components.
<div> <filters on-selection-changed="vm.filterProductList(filters)"></filters> <list items="vm.products"></list> <paging number-of-items="vm.getTotalNumberOfProducts()" on-page-changed="vm.updateProductList(page)"></paging> </div>
This little piece of code can change the way you write AngularJS applications! Take a moment and check out the interface definitions of each component. They are incredibly clean. Without seeing any controller logic or any of the child components, we can easily imagine the logic behind them.
Let's take a look at the list component first. We bind the array of products (
vm.products) that is available in the products overview component to the
items variable that is available in the scope of the product table component. We semantically translate products to items, so in the list component we are totally agnostic of what kind of items we show. We cannot only bind a list of products to that component but also a list of users or a list of your favorite dishes or a list of cities or any other kind of list that you can think of. Since there is nothing else defined in the interface of our list component, it will solely focus on rendering the items it is bound to.
The next step is the filters component. We can see that we do not bind any data to the filters component. That means, that all necessary data is directly defined in the component itself. But we see a callback with the name
on-selection-changed. Again we semantically translate the action that will be called in the filter component to the action that will be applied in the parent component. As soon as the user selects or deselects a checkbox the
onSelectionChanged function must be triggered which is bound to the
filterProductList function in the parent component resulting in the
vm.products array to be updated. Since our list component is bound to that array, it will automatically re-render the view.
Last but not least we will analyze the paging component. Here we can see both data binding and a callback definition. We tell the paging component how many items we have in total by binding it to
number-of-items. At the same time we define a callback
on-page-changed that will be triggered, when the user wants to scroll through the list of products and selects a different page number. This in turn will lead to the
vm.products array to be updated to contain the next load of products and showing them in the list component. The internal state of the paging component is completely handled by the component itself. It will figure out how many pages it needs based on the page size and the total number of items. And it will trigger the callback function as soon as the user interacts with the controls defined in the component's template.
Wrapping it up
With only a few lines of code, we learned the principles of the reactive pattern in AngularJS. I know, a lot more code is necessary to make the given example fully functional and if you want me to provide a working example, let me know so I can upload it to Github and link it here. But the idea should be clear by now. We only pass data into components that is necessary for the specific purpose of that component (Single Responsibility Principle). We clearly define callback actions that will be triggered, when something happens in the component. We can then handle this callback and modify the data where it is defined and bound to. That way we make sure, that the data flow is transparent and comprehensible for everyone looking at the code. By keeping the interfaces of components as simple as possible we can easily write tests for them und keep the whole application modular (Interface Segregation Principle). And by semantically translating the data when binding it to a component we make the components agnostic of their environment and thus can reuse them throughout the project.
Beautiful, isn't it?