Sending Actions Up to the Data Owner

The concept of data owner is important in the “Data Down Actions Up” (DDAU) paradigm.

We call data owner to that application component that takes responsibility for manipulating, loading or persisting a particular piece of data.

A component will typically receive an argument with data to render (data down) from the data owner. Since the goal of DDAU is that only data owners mutate their own data, other components need to be able to communicate back to the data owner to indicate changes (actions up).

That’s right, we don’t want to rely on two-way bindings for mutating state. Instead, we want to invoke functions that convey the mutation.

Closure actions

Closure actions are functions wrapped in a function. When the action helper is called it creates a function with a closure that captures the scope and arguments. We are now free to pass these functions around and invoke them in their original context.

Given a component named parent:

here is how it would include a reading-list component:

Whenever a book is added, the reading-list component will invoke its addBook attribute: the add closure action. This is how the component is able to tell the data owner (parent component) that books should be updated to reflect a new book added to the array.

Notice how elegantly each component deals with its own responsibilities.

So, books=booksArray is the data down part of DDAU, and addBook=(action 'add') is the actions up part of DDAU.

In the Ember stone age, we were using this.sendAction() with named actions which would be invoked in “element space”. A sendAction would trigger an event that bubbled up the chain until it found an action with that name. Intermediate components in the hierarchy had to pass that name around.

This approach had quite some drawbacks:

  • we shouted into the ether and hoped somebody was listening - error-prone and not very reliable
  • sendAction does not return any value; since closure actions return functions, we can use return values which is a major advantage
  • closure actions fail immediately if the action is not found - by design, traditional actions would lazily raise errors only upon invocation
  • everything is now uniformly treated as a value - no “special” treatment

So (action) will look for actions in:

But what if the route is the data owner?

Actions up to the route

The getting started guide also featured a reading-list component.

There, the application route is the data owner: it loaded and persisted books from/to a database.

Let’s quickly recall how that looked:

At the bottom of the diagram, notice how we pass in an onEnter action (belongs to the reading-list component) to the input component.

When the enter key is pressed, onEnter gets the input title and creates a book object. How does it send that book object up to the data owner (the route)?

Simple enough, sendAction can bubble up to the route, right?

// in the component

actions: {
  onEnter(title) {
    let book = { title };
    this.sendAction('addBook', book);
  }
}

That is correct. But we are back to old ugly sendAction. Luckily we have a much better option that does not require intermediate components/controllers to “catch-and-throw” action logic.

It is a fantastic add-on called ember-route-action-helper.

$ ember install ember-route-action-helper

If you paid attention in the diagram above, this is how we are passing in the route action:

{{reading-list books=model addBook=(route-action "addBook")}}

And we can refactor our component to use that function:

// in the component

actions: {
  onEnter(title) {
    let book = { title };
    this.get('addBook')(book);
  }
}

Remember that closure actions return values – that is invaluable.

If the route returned a promise (which is very common), we can catch errors and render appropriately:

// in the component

actions: {
  onEnter(title) {
    let book = { title };
    this.get('addBook')(book).then((savedBook) => {
      // do something else with saved book
    }).catch((e) => {
      this.set('error', e.message);
    });
  }
}

In 2027, when “routeable components” land in Ember, we will simply replace route-action with action. Neat and tidy.

The mut helper

Updating a component property after some user interaction is indeed very common. Ember has us covered: a mut helper that will act as a “setter” – significantly DRY’ing up our code.

We use it as a subexpression with the action helper:

{{input title onEnter=(action (mut title)) }}

Which is equivalent to:

{{input title onEnter=(action 'setTitle') }}

PLUS the corresponding action:

// component

actions: {
  setTitle(title) {
    this.set('title', title);
  }
}

So, which one do you prefer?

More complex components such as a date-picker may deal with objects instead of strings. With value we can essentially “pluck” a property of the returned object.

In this date picker, we want to get the moment.js value of the date. Instead of retrieving date.moment in an action, we do everything in the template:

{{date-picker
  selected=(readonly date)
  minDate=now
  required=true
  onSelect=(action (mut date) value="moment")
}}

Very elegant.

Do you use any of these tools? Which one you prefer and why? Let me know in the comments below!

Enjoyed this article? Don't miss my next one!

Leave me your e-mail for content that will help you master Ember:

Do you want to master Ember fast?

Leave me your e-mail for helpful updates delivered straight to your inbox.

(A few e-mails per month. No BS. Unsubscribe anytime!)