One-way Data Flow (in AngularJS 1.6)

Alex Weisberger
Building VTS
Published in
5 min readAug 21, 2017

--

There can be only one

Managing state is complex and error-prone. We want certain variables to always have the correct value, and for that value to be reflected in the UI. This problem has caused so many errors across so many applications that, as an industry, we have created many patterns for wrangling it. One such pattern that has a good amount of consensus (especially in the JavaScript community) is one-way data flow.

The Example

One-way data flow has been described ad nauseam around the Internet. Let’s dive into an example to illustrate it rather than describe it in words. We will be building a view for looking at an Asset Manager’s retail asset (a realistic use case that we tackle on VTS). This asset manager is responsible for a retail shopping center, which most importantly entails keeping all available spaces leased. Any current or approaching vacancy means the asset can quickly turn into a liability. We will provide a way to inspect an asset’s spaces for leasing data in two different ways:

  1. A Site Plan view which provides a visual depiction of the physical layout of the spaces
  2. A Tenant List which shows the currently occupied tenants in alphabetical order

Finally, there will be a third Space Detail view that provides information about the currently selected space. These components will be contained in a Retail View. Here is what it will look like:

Everything is NSYNC

A space can be selected via the Site Plan or the Tenant List, and all three views will be kept in sync with that space. Let’s use one-way data flow to accomplish this behavior.

The Component Tree

It helps to envision a view as a tree of components, with a clear parent / child relationship. To model the above example, we will have a parent component with three child components. The parent will be the sole owner of the selectedSpace variable. That variable will be passed into each of its children via one-way bindings (i.e. “<”), ensuring that its value is always consistent in all components. Here is the data flow of this component tree:

Data flowing downward from parent to children

All of the arrows are pointing in the same direction: downward from parent to child. One-way — beautiful! Now, if the Retail View is the sole owner of the selectedSpace variable, how do we modify it from the child components? The answer is with actions.

Data Down, Actions Up

Instead of mutating a value within a child component, we can instead trigger a callback that was passed in via a “&” binding. This allows the parent to specify the action that should happened in response to an event that occurred in the child. Here’s what that looks like:

Actions flowing upward from children to parent

The Site Plan signals when one of its spaces is selected, the Tenant List signals when one of its tenants is selected, and the Space Detail signals when its close button is pressed. Notice how none of these have to do with mutating any values or have any knowledge of which space is currently selected. The children simply say when certain events occur. The callbacks that are passed in from the parent will handle any mutation. Actions up!

The Code

All code is available on GitHub. First let’s look at the parent: Retail View

Retail View manages two variables: spaces and selectedSpace. Only selectedSpace is mutable, and it’s only mutated within this component which greatly reduces the complexity in tracking down the cause of its current value. In its template, we have the three children components (site-plan, tenant-list, and space-detail) with any bindings they require passed into them. Let’s call out two properties of the children:

  1. All child components receive selectedSpace as data
  2. All child components receive callbacks from Retail View for responding to events. These callbacks are where selectedSpace is actually modified, in the context of Retail View.

These two properties are the essence of one-way data flow.

Let’s dive into one of the children, Site Plan, to see how it behaves:

The Site Plan has two goals:

  1. Accurately reflect the state of selectedSpace in the UI
  2. Influence the state of selectedSpace by triggering the onSpaceSelected action.

It accomplishes 1. by adding an asterisk to the currently selected space via this line:<span ng-if=”$ctrl.isSelectedSpace($index)”>*</span>. It accomplishes 2. by triggering an action when any button is clicked via this line: ng-click=”$ctrl.selectSpace($index)”. Let’s walk through the flow of what happens when a button is clicked to see how selectedSpace ultimately gets modified.

First, selectSpace is called which is defined as this:

selectSpace(index) {
this.onSpaceSelected({ space: this.spaces[index] });
}

As we remember, onSpaceSelected was passed in from the parent in the template of Retail View:

<site-plan
spaces="$ctrl.spaces"
selected-space="$ctrl.selectedSpace"
on-space-selected="$ctrl.setSelectedSpace(space)">
</site-plan>

Finally, setSelectedSpace is defined in Retail View as:

setSelectedSpace(space) {
this.selectedSpace = space;
}

Retail View specifies that the currently selected space should be set to the space that was selected in Site Plan. Since selectedSpace is one-way bound to each of the child components, they will all now re-render and the UI will correctly reflect its value. Success! Go ahead and check out the other children’s code, but they all operate on this same idea.

In Closing

VTS switched to a single page application in 2014. Even though Angular is not as dominant as it once was and there are several competing frameworks to choose from currently, we can still take advantage of the patterns that have been espoused in these other frameworks. One-way data flow is a great example of this. It allows us to create complex interactions in the UI without losing track of who owns and mutates state. It also facilitates testing, especially in children components, because they depend on that state instead of owning it. In that case, we can simply pass in the desired value for test contexts.

When it comes to data flow, there can be only one… way! 😊

--

--