The Tale of Unidirectional Dataflow in Angular
When it comes to Angular, the framework has its perks and quirks. While React implements a unidirectional dataflow pattern by default, Angular didn’t exactly go down this route from the get-go.
Unidirectional dataflow is a conceptual pattern that is used by many frameworks and libraries on the front end — recently popularized by the rise of Redux patterns over the past few years.
But what exactly is unidirectional dataflow, what’s it got to do with immutability, and why is it awesome in Angular?
The Lowdown on Unidirectional Dataflow
Unidirectional dataflow is not a word we often see, nor is it something that organically comes up in conversations. However, it’s a neat little data management pattern that can simplify how your application responds to change.
For most apps, everything eventually boils down to data. The flow of data between the different layers, such as views, components, and services, can determine the effectiveness of an application’s modularity as it grows.
So what exactly is unidirectional dataflow?
Unidirectional dataflow is a programming pattern that deals with how data is updated. There are two main patterns currently in use — unidirectional and bidirectional. The uni part of unidirectional refers to the idea that data can only flow in one direction.
For the front end, once a view has been rendered, an action is required to change the data and re-render the entire or portion of a view from scratch.
React does this by default and there is no explicit binding available to allow the view to update without direct action from the layers beneath it.
Updates happen in one direction and one direction only. The data is not expected to change and is therefore considered immutable. When something happens and a change occurs, that state no longer exists and it is considered a new entity entirely by the view.
Change is caused by an external action. When this happens, the view is wiped clean and re-rendered with the new data. This is unidirectional in a nutshell: a one way dataflow where data can only move in one direction and cannot backtrace in the direction it just came from.
With unidirectional dataflow, there’s no need to track states because a change in data will result in a complete change in the view.
To understand this in an Angular context, we need to travel back in time — way back to the original iteration of Angular.js
A Little History in Time
In Angular, update bindings on the view happen during the change detection. When there is change detection, data changes in the children have the potential to update the parent too, rather than remaining isolated. It’s sort of like implementing a reversed inheritance pattern that allows parents to take values and properties from their children.
On the surface level, changes in the view can appear the same for both bidirectional and unidirectional dataflow. However, on a data relationship level, a bidirectional approach can be somewhat problematic.
In Redux, data is placed in a single logical space that has a standardized method of access and change. Actions are required to do things to the data or retrieve it. This pattern is effective and growing in popularity because data is centralized and can only be changed in one place, rather than mutated by multiple components.
In Angular.js (aka Angular 1), two-way binding made the movement of data between different parts of the application easy. However, this posed a major problem when components begin to act in an undesirable manner due to the side effects of bidirectional dataflow.
In the diagram above we have a hypothetical application with a parent component rendering two other children components. The variable
cat is called to the front end and rendered. In this case, the value is
Tibbers. However, things can get messy with a bidirectional dataflow approach.
In the diagram above, when the variable
cat is changed to
Bob, the data flows back up the way it trickled down, creating a reverse cascade effect on the data displayed. Perhaps you didn’t want the parents of
Child 2 to update the data.
Here is an expanded example of when bidirectional dataflow gets messy as the application grows.
In the diagram above, if
Child 2 changes, then both parent components will be impacted — even though they are independent of each other. Data that’s supposed to be isolated become interconnected. This is an issue because as the application grows, there are more potential points of change and therefore more potential points of failure.
With a unidirectional approach, each component is separated and the states become containerized. The inability to flow data back up creates a linear pathway for change to occur.
While a bidirectional approach does make life easier, because you don’t have to explicitly instigate change through actions, it doesn’t scale as easily as a unidirectional approach.
But Can’t You Do Bidirectional in Angular 2+?
Not anymore! Two-way binding is different from bidirectional dataflow. Angular 2+ now implements a unidirectional flow of data — meaning that the view cannot be updated once it has been composed.
Each time a view is rendered, Angular goes through a life-cycle of creating, checking, composing, and destroying a view. This means that the view essentially gets ‘repainted’ each time something changes.
We don’t see this happen because of how quickly it happens and the change feels almost instantaneous.
Two-way binding refers to the relationship between the service and its associated view. Bidirectional and unidirectional dataflow refers to boundaries, domains, and direction data moves between services and views.
Binding refers to a singular one-one-one relationship, while bidirection and unidirection refers to the relationship between components.
The direction of dataflow is often confused with the concept of two-way binding. This is because both things are related to data.
Unidirectional flow is more to do with the data and how it interacts with the other components based on their relationships with one another. Two-way binding is more limited to the view and its interaction with its associated servicing components.
Understanding how dataflow works can help you figure out why things go wrong and where they go wrong. The implementation of a unidirectional dataflow pattern in Angular means that only children components can inherit. Parents no longer reserve this right.
Sometimes this can cause problems if you want the data to flow backward, for whatever reason. That’s why a redux pattern is often employed to centralize data, whilst maintaining a unidirectional dataflow.
With Redux, data is decoupled from the component and put into a single space with a standardized method for retrieval and change. This means that flow-back inheritance isn’t a problem any more, as data is retrieved from a single store rather than from the children.
This also means that data can be shared between components without the need to break boundary rules and pass data around. There is no tracing required — just a connection to the central storage.