Quantcast
Channel: Ember.JS - Latest topics
Viewing all articles
Browse latest Browse all 4828

Readers' Questions: "What is meant by the term 'Data down, actions up'?"

$
0
0

@jessica wrote:

Hello once again to Readers’ Questions, presented by the folks at The Ember Times :newspaper::slightly_smiling_face:

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 a Route, but it could also be a data loading Component; on the other hand the recipient of passed down data is oftentimes another sub route or Component.

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 or Controller 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 name foo in the respective app/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 the user 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 the language-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 the select element changes. This callback will then be invoked on the parent (the profile 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 the profile 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! :sparkling_heart:

Posts: 1

Participants: 1

Read full topic


Viewing all articles
Browse latest Browse all 4828

Trending Articles