What unidirectional data flow really means in Angular

Architectural patterns are often hard to wrap your head around, especially when there’s little solid documentation to rely on. Angular’s unidirectional data flow is one such pattern. The official docs only touch on it briefly in the expression guidelines and template statements sections, and I’ve yet to find a thorough explanation elsewhere. This post aims to fill that gap.

Two-way data-binding versus unidirectional data flow

Unidirectional data flow frequently comes up in discussions about why Angular outperforms AngularJS. It’s a central factor, so let’s dig into where this pattern actually applies.

Both frameworks share a similar approach to component communication through bindings. Suppose you have a parent component A defined in AngularJS like this:

app.component('aComponent', {
  controller: class ParentComponent() {
    this.value = {name: 'initial'};
  },
  template: `
    <b-component obj="$ctrl.value"></b-component>
  `
});

----------------

app.component('bComponent', {
    bindings: {
        obj: '='
    },

Here, the parent A passes the value down to the child B through the obj input binding:

<b-component ****obj="$ctrl.value"****></b-component>

Angular does something very similar:

@Component({
    template: `
        <b-component [obj]="value"></b-component>
    ...
export class AppComponent {
    value = {name: 'initial'};
}

----------------

export class BComponent {
    @Input() obj;

The key point to grasp is that both Angular and AngularJS refresh bindings as part of change detection. When the framework runs change detection on the parent A, it assigns the child’s obj property accordingly:

bComponentInstance.obj = aComponentInstance.value;

This is one-way data binding, or unidirectional data flow, moving from top to bottom. Where AngularJS goes further is that it can also propagate changes back up from the child to the parent’s bound value:

app.component('parentComponent', {
  controller: function ParentComponent($timeout) {    
    $timeout(()=>{
      console.log(this.value); // logs {name: 'updated'}
    }, 3000)
  }
  
----------------
  
app.component('childComponent', {
    controller: function ChildComponent($timeout) {      
      $timeout(()=>{
        this.obj = { name: 'updated' };  
      }, 2000)

In that snippet, you see two timeouts. The first updates the child’s property; the second, firing a second later, checks whether the parent’s property was also changed. Running that in AngularJS reveals the parent property does get updated. Here’s why.

When the first timeout callback executes and the child B’s obj becomes {name: 'updated'}, AngularJS kicks off change detection. During that pass, it spots the change in the bound child property and syncs the parent’s value property. This behavior is baked into AngularJS’s change detection engine. In Angular, the same scenario only updates the child property—the parent stays untouched. That’s a fundamental difference in how the two frameworks handle change detection, but one thing still bugged me for a while.

Angular does provide a way to update a parent from a child, via output bindings. You can write:

@Component({
    template: `
        <h1>Hello {{value.name}}</h1>
        <a-comp (updateObj)="value = $event"></a-comp>
    ...
export class AppComponent {
    value = {name: 'initial'};
    
    constructor() {
        setTimeout(() => {
            console.log(this.value); // logs {name: 'updated'}
        }, 3000);
        
----------------

@Component({...})
export class AComponent {
    @Output() updateObj = new EventEmitter();
    
    constructor() {
        setTimeout(() => {
            this.updateObj.emit({name: 'updated'});
        }, 2000);

Sure, it’s not the same as modifying the input directly from the child, but the parent still gets updated. So why isn’t this called two-way data binding? Communication clearly happens in both directions.

Then I came across Two Phases of Angular Applications by Victor Savkin, and it clarified things:

Angular 2 separates updating the application model and reflecting the state of the model in the view into two distinct phases. The developer is responsible for updating the application model. Angular, by means of change detection, is responsible for reflecting the state of the model in the view.

It took me days to fully absorb that. The parent update via an output binding:

<a-comp ****(updateObj)="value = $event"****></a-comp>

isn’t part of change detection at all. It happens in the first phase, before change detection runs, when the application model is being updated. So unidirectional data flow refers specifically to how bindings are updated during change detection. Unlike AngularJS, Angular’s change detection has no logic that pushes child property changes back to the parent. Output bindings process outside change detection, so they don’t turn unidirectional data flow into two-way data binding.

On a side note, while Angular lacks a built-in mechanism to update a parent’s model during change detection, it’s still doable via a shared service or synchronous event broadcasting. Because the framework enforces unidirectional data flow, doing that leads to the infamous ExpressionChangedAfterItHasBeenCheckedError. For a deep dive into that error, its triggers, and how to handle it, check out Everything you need to know about the `ExpressionChangedAfterItHasBeenCheckedError` error.

Unidirectional data flow across view and service layers

Most web apps are built with two main architectural layers: the view or presentation layer and the service layer.

Do you really know what unidirectional data flow means in Angular — figure 1

In a web context, the presentation layer handles showing application data to the user in the DOM. In Angular, components make up this layer. The service layer deals with processing and storing business data. As the diagram above shows, it can be divided into state management and infrastructure pieces, such as REST services or reusable helper utilities.

Unidirectional data flow, as described in the first section and referenced in Angular’s docs, applies to the presentation layer—that’s the layer built on components.

Do you really know what unidirectional data flow means in Angular — figure 2

That said, the rise of ngrx, which implements a redux-style state management pattern, has introduced some confusion. Redux’s docs describe:

Redux architecture revolves around a strict unidirectional data flow.
This means that all data in an application follows the same lifecycle pattern, making the logic of your app more predictable and easier to understand…

That particular unidirectional data flow is about the service layer, not the presentation layer. Still, I often see explanations that conflate the two, linking redux’s pattern with Angular’s. That’s worth keeping separate. The unidirectional data flow redux mentions is not about the presentation layer. It concerns the service layer, specifically state management, and shifts the architecture we saw earlier:

Do you really know what unidirectional data flow means in Angular — figure 3

into this form:

Do you really know what unidirectional data flow means in Angular — figure 4