Two-way data binding (Angular) vs one-way data flow (React/Flux)
Asked Answered
B

4

31

In the last week, I’ve been trying to make sense how two-way data binding (Angular) and one-way data flow (React/Flux) are different. They say that one-way data flow is more powerful and easier to understand and follow: it is deterministic and helps avoiding side-effects. In my newbie eyes though, they both look pretty much the same: the view listens to the model, and the model reacts on actions done to the view. Both claim that the model is the single source of truth.

Could anybody comprehensively explain in understandable way how they are really different and how one-way data flow is more beneficial and easier to reason about?

Bently answered 2/1, 2016 at 11:51 Comment(0)
L
18

Angular's two-way data binding

It's made possible by a mechanism that synchronizes the view and the model whenever either change. In Angular, you update a variable and its change detection mechanism will take care of updating the view, and viceversa. What's the problem? You don't control the change detection mechanism. I found myself having to resort to ChangeDetectorRef.detectChanges or NgZone.run to force the view to update.

To not dive too deep into change detection in Angular, you trust it will update what you need when you change a variable, or when it gets changed after an observable resolves, but you'll find you have no idea how and when it runs, and sometimes it will not update your view after a variable changes. Needless to say, it can sometimes be a pain to find where and when a problem occured.

React's one-way data flow

It means that the view always gets its state from the model. To update the view, you need to update the model first, and then redraw the view. React makes the view redrawing process extremely efficient because it compares not the actual DOM but a virtual DOM it keeps on memory. But how does change detection work in this dynamic? Well, you trigger it manually.

In React, you set the state's new value, which then causes a ReactDOM.render, which causes the DOM comparing/updating process. In React/Redux you dispatch actions which update the store (single source of truth) and then the rest. Point is, you always know when the stuff changes, and what caused the change. This makes problem solving quite straight forward. If your app depends on the state, you look at it before and after the action that triggered the change, and you make sure variables have the value they're supposed to.

Implementations aside

From a platform independent point of view, they're not so different. What separates one-way flow from two-way binding is a variable update on change. So your impression that that they're conceptually not too far from each other is not too divorced from their practical uses.

Laicize answered 13/6, 2018 at 19:12 Comment(5)
So in React, you set the model, and then it automatically updates the view for you, and in Angular you update the model, and then it automatically updates the view for you. But you're saying that in Angular it doesn't always work, and thus you have to manually force the view to update sometimes? That sounds to me like Angular was just implemented poorly with leaky abstractions, not that one way data flow is inherently easier to reason about. Eg. if you were able to trust that Angular will update the view when the model changes, it would be just as easy to reason about...Continuance
... And if you weren't able to trust that React will update the view when you change the model, then it would be similarly difficult to reason about - you'd have to manually force updates just how you currently have to force the view to update with ChangeDetectorRef.detectChanges and NgZone.run in Angular right now.Continuance
As for the fact that with one way data flow, you are always the one to update the model manually, which allows you to log all mutations to the model, which allows you to easily debug - I agree that that is an awesome benefit, but it doesn't seem like it is an inherent property of one way data flow, and it doesn't seem like it is inherently not a property of two way data binding. Eg. with one way data flow, if you don't log the mutations, you don't have that trace to help you debug.Continuance
And with two way data binding, the framework could be written such that it logs the view → model mutations when the model is automatically updated, just like Redux does. Eg. say you have ng-model on an input field, you type "hi", Angular automatically does the view → model update, and along with this update it logs the mutation that happens so that you can trace changes when you're debugging. My point is that the logging feature seems different from one way data flow versus two way data binding, and thus shouldn't be counted as a benefit of one way data flow.Continuance
I gave a platform dependent explanation of why one is easier to reason about than the other given their (perhaps not optimum) implementations in Angular and React. I did not attempt to provide a platform independent analysis. but If I were, I wouldn't say one is inherently easier to reason about than the other, because this is directly correlated to the implementation.Laicize
H
12

In Angular you have many controllers. One example would be a user triggering an action on View 1 that is managed by Controller 1. Controller 1 does something but also fires an event that is caught by another Controller 2. Controller 2 updates some property on the $scope and View 2 is suddenly changed.

Suddenly an operation on View 1, updated View 2. If we now throw in some Async callbacks and a bit more event chains, you might no longer know exactly when/how your views are being updated.

With Flux/Redux, you have a one way data flow. The view never updates the model, the views can only dispatch an action (intention to update), but lets the store/reducer deciding how to handle the update. You can more easily reason about the data flow because you can easily see which actions can be fired by each view. Then follow up to see how that action is being handled by the store and you can know exactly what can be updated.

Hummer answered 2/1, 2016 at 13:19 Comment(5)
In your Angular example above, isn't it the same as saying in React/Flux: Store 2 registers a callback with the dispatcher on action triggered by View 1, and then triggers an event to View 2 to update? It's still an operation on View 1 updates View 2, and it appears to me that this implementation in React/Flux doesn't quite give added value compared to when I implement it in Angular. Not sure how "async callbacks and a bit more event chains" will change the game. Could you please elaborate more on it?Bently
Really great answerIchthyoid
@GlennMohammad With Flux (or Redux), you keep track of any intention. Each intention of mutation traverses the Dispatcher; so you are aware of any action in your app without so much effort. With the Angular examples, you would face to implicit intentions/actions without any constraint on their transmission. This would be very hard to reason about.Ichthyoid
You can imperatively change View 2 from View 1 in Angular, but the preferred way to do it is to use a factory for the shared data and use dependency injection to access the factory from whichever controller needs it. That approach seems very similar to one way data flow in the sense that you update the shared data store, and then the data flows down into the components, updates their state, and the views get rerendered with that new state. I'm not seeing how you would reason about the data differently.Continuance
@Ichthyoid the fact that all mutations are done via a function and recorded doesn't seem like it is a necessary part of one way data flow to me. Ie. it seems to me that it would still be one way data flow without that part. Also, it is worth noting that the recording of mutations could be done in Angular as mentioned in the second part of this answer.Continuance
B
6
  1. Data flow here is a flow of write events - i.e. state updates

  2. These events are flowing between views and controllers (and services, such as HTTP backends)

  3. One-way flow is basically the giant cycle:

    • app view uses (reads, not writes) app state to render
    • when application gets some stimuli from outside (user typed some text in input field, or result of HTTP request has arrived), it emits write event - or, in Redux/Flux slang, dispatches an action
    • all events, from all controllers and views, are flowing into the single sink - dispatch function (reducer); although the nature of dispatch function allows it to be composed from simpler dispatch functions, conceptually, there's only one dispatcher for the whole app
    • dispatcher uses an event to figure out which part of the state is to be updated
    • go to start
  4. Two-way flow aka data binding binds two pieces of state: in most cases, one inside the controller (e. g. some variable), and one inside the view (e. g. contents of textbox). Binding means that, when one piece changes, the other piece changes as well and gets the same value, so you can pretend that there's only one piece of state involved (while there's two actually). Write events are going back and forth between controllers and views - thus two-way.

  5. Data-binding is cool when you need to figure out what variable holds the contents of this particular textbox - it shows immediately. But it requires complex framework to maintain the illusion of one piece of state where there's two pieces really. Usually you'll be forced to use framework-specific syntax to write your views' code - i. e. to learn yet another language.

  6. One-way data flow is cool when you can leverage that extra entity - events flow. And, usually, you can - it's useful for Undo/Redo, user actions replay (e. g. for debug), replication, etc, etc. And the code to support this is much, much simpler, and usually can be written in plain JavaScript instead of framework-specific syntax. On the other hand, since you no longer have data-binding, it no longer saves you some boilerplate.

Also, see great visual explanation in this answer: https://mcmap.net/q/156133/-can-anyone-explain-the-difference-between-reacts-one-way-data-binding-and-angular-39-s-two-way-data-binding. Single-headed and two-headed arrows visually represents one-way and two-way data flow respectively.

Blanco answered 5/12, 2017 at 11:41 Comment(0)
D
5

Let's say your app is just a wizard flow, but it has some complex interactions i.e. one step might change a following step behavior.

Your app is running great, but one day an user reports a bug on one of the tricky steps.

How does debugging would work on two-way binding and one-way binding?

Two-way binding

I'd start checking what behavior is different and with some luck, get to the same point as the user and pinpoint the bug. But at the same time there might be some weird interaction between different parts of the app. I might have some data-binding that is incorrect (e.g. replicating the model state but not binding) or other weird intricacy between components that is hard to debug. It might be hard to isolate the bug.

One-way binding

You just grab the state object. It has all the information of the app currently in a big javascript object. You load the same state in your development environment, there is a big chance your app will behave exactly the same. You can even write a test with the given state for regression and pinpoint the exact problem that is happening.

Conclusion

In a few words, one-way binding makes it very easy to debug complex apps. You don't have to do much then copy over the current state of the user.

Even that doesn't work, you can log the actions as well. There isn't AFAIR an easy way to track all the state modifying actions on Angular, for instance. With Redux it's pretty, pretty easy.

Dolerite answered 20/2, 2017 at 17:8 Comment(3)
Angular and React both allow you to declaratively describe your views and the framework fills in the data for you. So in Angular, if you logged the state you would also be able to take that state and experience the same bug the user was having. The difference I see is that Redux keeps track of the actions that lead to that state, and so you could easily see what lead to the bug. I agree that that is a great feature.Continuance
However, 1) It is possible to implement it in Angular by writing setter methods that log when they're called. 2) Keeping track of the actions seems like a different thing all together than one way data flow. Ie. AFAIU, one way data flow happens when you update a shared state and that state flows down to components, which then rerender their views with the new data. That could be done without actually logging actions and mutations like the Redux implementation does.Continuance
You're right, but being able to do these in Angular is not necessarily how you will see people doing it. Redux helps by having them baked-in. What you said is pretty much true on any javascript framework out there.Dolerite

© 2022 - 2024 — McMap. All rights reserved.