Calling a route action from a component fails


#1

I tried to use sendAction to call another route action from component like that:

# components/holiday-hour.js

actions: {
...
    deleteHoliday(holiday) {
      this.sendAction('deleteHoliday', holiday);
    },
  }

I have deleteHoliday action defined in holiday-hours.jsrouter:

# routes/holiday-hours.js

actions: {
...
deleteHoliday(holiday) {
      console.log('ROUTE-> deleteHoliday called');
      let holidayToDelete = this.store.peekRecord('holiday-hour', holiday.get('id'));
      holidayToDelete.destroyRecord().then(function() {
        this.get('flashMessages').success(route.get('i18n').t('flash.holiday.removed'));
      });
    },

Here is how the action is triggered from the component template:

# templates/components/holiday-hour.hbs

<button type="button" class="btn btn-danger btn-sm mt-1" {{action 'deleteHoliday' holiday}}>
      <span class="oi oi-trash"></span>
</button>

And it is never called :frowning: What is wrong with that ? Thank you.


Actions in routes
#2

Hi,

Contrary to what the doc that your link pointed to says, and this snippet from an Ember book:

sendAction(‘onClick’, …) sends the action name passed in to the component (updateRating in the above case) with some optional parameters. That action will be handled by the first handler on the “controller, then current route, then active routes” path. In our case, the bands.band.songs route will have the privilege to do so.

It looks like the action must be defined in the controller, not the route.

For example, I have a component and added the following anAction param to to the way I called it:

{{users/settings-form user=model
                  selectedState=(user/select-state model)
                  anAction=(action 'myAction')
                  onSubmit=(action 'register')}}

Then I called it from my component, thusly:

  this.sendAction('anAction');

When I put the myAction handler in the controller it works fine:

    myAction() { alert('got it'); },

However, when I remove that action from the controller and put it in the route, my app doesn’t even display the page due to the following error:

Error: Assertion Failed: An action named 'myAction' was not found in <bnc@controller:users/register::ember487>

I have been burned on this before, and have found it confusing as to what the rules actually are for calling actions from components.

For example, there was a long discussion in here (Where should I define a `save` action?) about this very issue. People went back and forth about whether or not the action(s) should be defined in the controller or the route… but that discussion struck me as moot when dealing with components, because in that case it seems that the only option is to put the action in the controller.

Also, it seems to me that sendAction is kinda sorta deprecated in favor of closure actions. From that same book:

From Ember 1.13 onward, there is another way to trigger actions. Instead of passing in an action name (a string) to the component, a function, returned by a call to the action helper can be passed in.

And here is the example he gives, with the old string way and the new closure action way:

{{star-rating item=song rating=song.rating onClick="updateRating"}}

{{star-rating item=song rating=song.rating on-click=(action "updateRating")}}

In this (closure) way, he points out that the action must live in the controller:

The action helper looks up the provided name in the actions object of the current context, in this case the controller and creates an action function from it encapsulating the current context (hence the name, “closure action”).

I’m just starting out, so I’d love to hear what someone more knowledgeable than I has to say on this subject.


#3

Ah, shoot. Looking over my post I see where I might have made an error: The param I set anAction to in the call to my component was a closure action, which means it has to live in the controller.

I was thinking that using sendAction would give me the controller -> current route -> active routes lookup path, but I guess I am wrong.

The operative word being “guess” => I’m leaving the original post the way it was, and maybe someone can clear this up - which will hopefully answer your original question as well.

The good thing about this discussion forum is that the answers will live forever => I doubt we will be the only 2 people who benefit from getting this question answered.

Thanks!


#4

Thank you for your response. It is still unclear for me. The difference with the latest docs and the latest RARWE book it that it is all about a click on a component itself or using services.

I wonder if the design I use is correct:

  • display a holidays rows in holiday-rows.hbs template
  • each row is represented by a component holiday-row.hbs
  • each row contains a button to delete the row the button belongs to

I prefer to have all the application scoped actions (like create, delete, update, etc.) to be defined in a route.


#5

I’m trying to apply closure actions way to do that but still no success:

#templates/holiday-hours.hbs
...
{{#each model as |holidayHour|}}
        {{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true deleteHoliday=(action 'deleteHoliday' holidayHour)}}
{{/each}}

Then in the component:

#components/holiday-hour.js

export default Component.extend({
...
  actions: {
    deleteHoliday(holiday) {
      this.get('deleteAction')(holiday);
    }
... 

The I defined the action in the route:

#routes/holiday-hours.js
export default Route.extend(AuthenticatedRouteMixin, {
...
    actions: {
      deleteHoliday(holiday) {
         console.log('ROUTE-> deleteHoliday called: state->' + holiday.get('state'));
....

Hoped that the action will be called in the corresponding route but it"s not the case, - Ember is trying to find it in the controller:

Assertion Failed: An action named 'deleteHoliday' was not found in <decastore-front@controller:holiday-hours::ember605>

Does it exist a way to call an upper layer action, - not from the controller but from the route ? Or to call a route action from the corresponding controller action ? Or the only way is to use ember-route-action-helper ?


#6

Using closure actions is the wrong way to go: doing so means that the action handler must live in the controller, not the route.

I think the mistake I made earlier boiled down to something like this: I assumed that the way I called the method would determine the lookup path, i.e. using sendAction meant the path would be controller -> current route -> active routes, but that was wrong.

What determines the lookup path is not how you call it, but what you call it with. Since the argument was a closure action, that meant the handler must live in the controller.

(Don’t ask me why because I don’t know. But it is one of those annoying aspects of programming that causes all of us to waste too much time: the problems you are experiencing have nothing to do with your domain logic, rather they are the result of the underlying implementation details.

Kinda like how programmers long ago had to deal with segmented memory in their code - had nothing to do with anyone’s problem domain, but had to be dealt with, nevertheless.) (And boy, was it a pita!)

Anyway, I think your first approach was pretty close; I think the only problem was the way you called your component, which you did not list (so I’m not sure).

Here is an example using different names, because I find that repeating the same name for both attribute and argument, i.e. deleteHoliday, only serves to confuse things. (At least it confuses me.)

In the template that calls the component, pass in the name of the route action-handler as a string:

{{myComponent myAction="myDelete"}}

In the component’s template do what you originally did:

<button type="button" ~~~ {{action "deleteHoliday" holiday}} ~~~>

In the component’s JS file:

actions: {
    deleteHoliday(holiday) {
       this.sendAction("myAction", holiday);
   }
}

And finally, in the route:

actions: {
    myDelete(holiday) {
       <delete holiday>
   }
}

I hope that’s right and that it solves your problem. Good luck!


#7

Thank you for your response. What do you think about the argument raised in this article regarding the use of sendAction saying that it had some drawbacks and which is rather old and acts more of an event bus that is not very efficient ?


#8

I’m really just starting out, but from what I have gathered, sendAction does seem to fit in “the old way of doing things”, and closure actions, i.e. “the newer way of doing things”, are considered “better” because given the fact that you actually call them means they can return a value that the callee can then use.

But just because it’s old doesn’t mean it’s necessarily bad, or even something to stay away from. I’ve heard of ember-oute-action-helper, but have no idea if that’s what people use these days.

Bottom line: someone who has been around ember for enough time to build up some historical context would be much better suited to answer this question.


#9

I absolutely agree with you (by the way I’m on Ember way for just 2 months as well). As for ember-route-action-helper, the very first line in their README tells:

You probably don’t need to use this addon. Most of the use cases you’d use this addon for are perfectly achievable without this addon.

So if I don’t find any other solution, I think I’ll have to install this add-on, because there is no clear and documented example in Ember guides on how to get out from this situation.


#10

I certainly feel your pain.

In my first response I included this link Where should I define a “save” action where a long discussion took place on where to put certain actions: in the controller or the route.

But nowhere did they talk about the difficulty of referencing an action in the route from a component, which stuck me as odd, given the emphasis on using components.

It seems like there should be a simple, well-documented way to do this, but unfortunately the two of us seem to be missing it. And even though it could be (probably is) our fault, I think it should raise a concern to the ember documentation folks that something this important can’t be figured out by newbies. (And I don’t mean to disparage them; they do a tremendous job on a difficult task on their own time for no pay - something few people are willing to do.)

Maybe someone with more smarts will show us the light. :smile:


#11

Yeah, as a proverb says: ’

hope springs eternal

:slight_smile:


#12

Anyway, I’ve just tried ember-route-action-helper, - it works fine and calls the action defined in the router:

# templates/holiday-hours.hbs

      {{#each model as |holidayHour|}}
        {{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true deleteAction=(route-action 'deleteHoliday' holidayHour)}}
      {{/each}}

Then in the component:

#components/holiday-hour.js

export default Component.extend({
...
  actions: {
    ..
     deleteHoliday(holiday) {
      this.get('deleteAction')(holiday);
    },

And finally in the route handler:

#routes/holiday-hours.js

export default Route.extend(AuthenticatedRouteMixin, {
...
actions: {
  ....
   deleteHoliday(holiday) {
      console.log('ROUTE-> deleteHoliday called:' + holiday.get('state'));
....