@jessica wrote:
Hello once again to Readers’ Questions, presented by the folks at The Ember Times
Today I’ll be answering a question by Daniel who is interested to know more about a particular Ember concept:
What is meant by the term “Data down, actions up”? Is it an important concept to learn?
“Data Down, Actions Up” has become one of the most mentioned patterns in the Ember ecosystem. But what does it mean?
What is meant by “Data Down, Actions Up”?
The term “Data Down, Actions Up” (DDAU) describes a common pattern found in Ember applications which emphasizes unidirectional data flow. The term describes the practice of passing data, e.g. in form of an Ember Data record or a plain JSON object, from one part of the app which “owns” the data down to other application layers. In Ember apps the data owner will oftentimes be a
Controller
or aRoute
, but it could also be a data loadingComponent
; on the other hand the recipient of passed down data is oftentimes another sub route orComponent
.The data recipient might have access to said to data to f.e. present it in the UI, but it is constrained in that it is never able to modify that data directly. Instead, with DDAU a component will only have access to one or several actions: callbacks which can be triggered through the component to modify data indirectly: these actions are passed down from the data owner and they will also be invoked on that very same parent layer they originate from.
This - at first - sounds a bit cumbersome. Why would you not want to modify the data directly where it is used and instead decide to pass down both data and callbacks for modifying that data to modify said data to another or potentially several layers of routes and components? What are the benefits of DDAU?
The advantages of DDAU
The benefits of DDAU can be broken down into the following three points that help us to develop Ember apps more easily:
DDAU helps with a clearer separation of concerns
Once we start to handle the mutation of data in one specific spot (in Ember we’d most often do so at the
Route
orController
layer) and only pass down data from this layer to components or a tree of components for presentation - it becomes clear to us where to look for the setup of data in our app right off the bat. Similar to the naming conventions we are used to from Ember’s application model, this convention helps us to save time ruminating on where our data might have to be setup. In a similar fashion that a developer with some previous experience with Ember is able to find a particular model of namefoo
in the respectiveapp/models/foo.js
path instantly, a developer who practices DDAU in their app can be sure to find the places in which data is being mutated in the blink of an eye, too. Therefore DDAU represents a benevolent convention which we find is helping us with reasoning about where data is originating in our codebase.DDAU helps with component reusability
Making our Ember components as reusable as possible helps us to avoid developing the same features repeatedly and also makes our codebase easier to reason about. DDAU enforces component reusability by removing responsibilities from the UI which may change depending on the context the component is used in.
DDAU helps to avoid complex data loops
Cycles in the application’s data flow can not only be hard to reason about but they can therefore also become prone to unexpected bugs easily. Once loops in our app’s data flow emerge, the application state might change in a time-sensitive manner and it is not very clear to which extent one function that mutates data “wins” over another.
To avoid having to manage cyclical data flows in Ember apps, DDAU emphasizes a clear, unidirectional data flow.
An Example of DDAU
Let’s take a look at a specific example. Let’s imagine we’d be building an application that has a
/profile
route. Users can edit their profile by visiting that route and also set the language in which they can view the app to be e.g. Portuguese instead of English.In the
/profile
route we can pass down theuser
data to the context:// app/routes/profile.js import Route from '@ember/routing/route'; export default Route.extend({ model() { return this.get('store').findRecord('user', 1); }, });
// app/controllers/profile.js import Controller from '@ember/controller'; export default Controller.extend({ languages: ['en', 'zh', 'hi', 'es', 'pt', 'fr', 'it', 'de'], });
As we build along this feature we might want to create a
language-picker
component along the way which offers a UI for making the changes to the user’s language settings.{{! app/templates/profile.hbs }} {{language-picker user=model languages=languages}}
The language picker displays the user’s current language choice and a list of different locales a user is able to choose from:
// app/templates/components/language-picker.hbs <p>Current Language: {{user.selectedLanguage}} <select> {{#each languages as |language|}} <option value={{language}}>{{language}}</option> {{/each}} </select></p>
And once a language is selected, it will 1. set the correct locale for the web app and 2. update the user settings to reflect that choice. We could handle all of this in an action - let’s call it
updateLanguage
inside of thelanguage-picker
component itself:import Component from '@ember/component'; export default Component.extend({ languages: null, user: null, actions: { updateLanguage(ev) { let language = ev.target.value; this.setupTranslations(language); this.set('user.selectedLanguage', language); }, }, });
And subsequently attach this action to the
language-picker
:// app/templates/components/language-picker.hbs <p>Current Language: {{user.selectedLanguage}} <select onchange={{action "updateLanguage"}}> {{#each languages as |language|}} <option value={{language}}>{{language}}</option> {{/each}} </select></p>
So far, so good. Yet, we can already see one problem with this approach: This component is only built for one specific use case - the setting of the user locale - and cannot be reused in other parts of the app easily. If we wanted to make this component reusable, we’d need to make sure that any changes to the application state which occur after selecting another language are not handled inside of the component itself, but in the external context instead.
We can make sure that any application state specific actions are triggered on a higher level, e.g. the
Controller
, by extracting said functionality and passing it down to the component as a callback which can be triggered through the component once the value of theselect
element changes. This callback will then be invoked on the parent (theprofile
controller in this example):// app/controllers/profile.js import Controller from '@ember/controller'; export default Controller.extend({ languages: ['en', 'zh', 'hi', 'es', 'pt', 'fr', 'it', 'de'], actions: { updateLanguage(ev) { let language = ev.target.value; this.setupTranslations(language); this.set('user.selectedLanguage', language); }, }, });
// app/templates/profile.hbs {{language-picker languages=languages user=model onLanguageChange=(action "updateLanguage") }}
// app/templates/components/language-picker.hbs <p>Current Language: {{user.selectedLanguage}} <select onchange={{action onLanguageChange}}> {{#each languages as |language|}} <option value={{language}}>{{language}}</option> {{/each}} </select></p>
Now the
language-picker
component gave up the responsibility of data mutation to the data owner - in this example theprofile
controller - making the component reusable in other contexts with different functionalities. The component now only presents the data which has been passed down to it and provides a handle for user interaction to trigger data and app state mutation externally.This way it is not possible for a circular data flow to emerge either. This allows data to be mutated with a predictable outcome.
It’s also important to note, that any data that is passed in an Ember app cannot be guaranteed to be immutable. Therefore DDAU is a useful pattern to make data flow predictable in Ember apps today. If you want to learn more about data immutability and how it could play out in future Ember apps, be also sure to check out @dgeb’s answer to one of our previous Readers’ Questions here.
Further Reading
The Data Down, Actions Up Paradigm is inspired by well-tested patterns practiced in other JavaScript communities. For example, you can learn more about the benefits of DDAU in this blog post about the separation of concerns through Container & Presentational Components in React.
If you want to learn more about the “Data Down, Actions Up” pattern in Ember specifically, I can highly recommend to give this discussion thread on the Ember forum a read and to watch this excellent (and free!) video on how to create reusable components featuring DDAU over at our friends’ at EmberMap.
This answer was published in Issue #60 of The Ember Times. Don’t forget to subscribe to the newsletter to get new Readers’ Questions and other development news right in your inbox. You can also submit your own questions! You will find a link at the bottom of the newsletter.
See you in the next issue!
Posts: 1
Participants: 1