Benefits of using action rather than pure function?


#1

Ever since I saw how actions are just normal functions in Glimmer.js, I dropped placing my actions inside an action object. Now my usage is {{my-component foo=(action myAction)}} instead of {{my-component foo=(action 'myAction').

I haven’t had any problem with this setup so far. Why do we still need action these days? Am I missing out on some benefits if I don’t place it under action object?


#2

I can think of two reasons.

  1. because actions are defined under the actions namespace it avoids name collisions. For example if you want an update action but also have an update function you can separate the two. There have been cases when this has helped but it also might suggest using better naming to disambiguate anyways.
  2. This seems more useful but by placing your functions into the actions section it clearly conveys to the casual reader your intent. One of the troubles with using functions like that is the reader doesn’t know right off if that function was passed in from the outside or defined in the current component while using the quoted version makes it clear that the action is defined in the component.

To further confuse the matter the very newest versions of ember will start using the @ and this symbols to disambiguate properties/functions passed in vs defined on the component itself. Which when widely available will no longer make point number 2 above relevant.

As far as I can tell at this stage of the ember standards, the difference between direct reference functions vs quoted actions is a matter of style. It was required way back when but today used more to help readers disambiguate the two while reading the code and will later be superseded by the built ins @/this in the future.


#3

@sukima @ / this looks interesting. Where can I learn more about this? Is it documented somewhere?


#4

The biggest reason historically was naming collisions, especially between people’s actions and built in methods on Component like destroy. People would get very weird failures when they tried to use a name like that that was already in use.

It’s already possible to use native Class syntax for components in most situations, and there we can use decorators to make the distinction unambiguous without a separate actions hash.

That is the likely plan going forward. Once we can stabilize the decorator API and document it through an RFC process, it can become the default thing that we teach in the documentation, etc.


#5

Hmm. Are you suggesting that we’ll have the @action decorator as a replacement to the action object? I don’t think that prevents the naming collision, will it?

If any, I think decorators like maybe @lifecycle solves the naming collisions from the perspective that this will run as part of the Component lifecycle

export default class Foo extends Component {
  @lifecycle
  didInsertElement() { ... } 
}

And this will not run as part of the lifecycle

export default class Foo extends Component {
  didInsertElement() { ... } 
}

Having this would still have naming collision if I’m correct:

export default class Foo extends Component {
  @lifecycle
  destroy() { ... } 

  @action 
  destroy() { ... }
}

#6

Right, it doesn’t make the collision impossible but it makes it statically detectable. So we could tell you at build time that you have a collision.


#7

The larger naming collision issue will be resolved when we no longer are doing Object.assign(componentInstance, argumentsPassed), which is one of the goals of the larger Glimmer Components in Ember project.


#8

I see. I’m still not pretty convinced though. IMO this is fine:

export default class Foo extends Component {
  destroy() { ... } 
}

As the destroy() wouldn’t be run as a lifecycle hook without the @lifecycle decorator. That’s why I think the @action is unnecessary. But this is probably a discussion for a future RFC. At least now I know that naming collision was one of the big issue why the action object exists. :+1:


#9

I was also under the impression that actions declared in the actions hash had the equivalent of a .bind(this) added? Otherwise the function called from the template does not have the expected this value set.


#10

The (action) helper is what takes care of that (binding and currying).


#11

It handles it for {{action 'myaction'}} but not for {{action myaction}} where myaction is a function declared directly on the component (not the actions hash).


#12

I don’t think that’s right. action binds this in all cases.

The difference between {{action 'myaction'}} and {{action myaction}} is quite small. It’s just whether to look in the actions hash to find the handler.

The bigger difference is whether they are being used as helpers vs element modifiers:

<button onclick={{action "doIt"}}></button>
<button {{action "doIt"}}></button>

Those situations are quite different. The first one is a helper, which returns a function (also called a “closure action”). The second one is an element modifier, or as the docs say “in element space”.

https://emberjs.com/api/ember/3.2/classes/Ember.Templates.helpers/methods/action?anchor=action