In the previous versions of ember.js you could pass actions down from a current component like
{{mycomponent myaction=(action "clickCategory) }}
Then you could go into mycomponent you could use another component in it:
{{mychildcomponent myaction2=(action myaction)}} /* without quotes */
and then in mychildcomponent template you can use it straight away without the need to put it in the js of mychildcomponent.
In the new Glimmer/Octane stuff, how is this done? The Ember Guides seem not to cover it so I am duplicating the actions in child components and calling up to this.args.myaction2().
Basically put can I pass an action down a few layers of components without having to define a @action in the js of each descendant?
// app/components/top-level.js
import Component from '@glimmer/component';
import { action } from "@ember/object";
export default class TopLevelComponent extends Component {
@action
handleClick() {
// your implementation here
}
}
{{! app/components/top-level.hbs }}
Passing the action from `this.handleClick`:
<MiddleLevel @handleClick={{this.handleClick}} />
{{! app/components/middle-level.hbs }}
Passing forward the named argument that we were passed (@handleClick):
<LowerLevel @handleClick={{@handleClick}} />
{{! app/components/lower-level.hbs }}
Using the named argument (@handleClick) passed in from app/components/middle-level.hbs:
<button {{on 'click' @handleClick}}>Click Me!</button>
Thanks a lot for your reply.
Do you know if it is possible for the top level component (as above, âtop-levelâ) to be a classic ember component, but the descendants be Octane (just as you have them)?
Curveball! But I always seem to run into these things straight away
The awkwardness comes with using the glimmer tag name in the top-level classic component html:
How do I express onInsertFurniture (an action from a classic ember component âtop-levelâ) in this new glimmer way of bringing in a component? Doing it the way stated there {{this.onInsertFurniture}} seems to be just undefined? It is not âseeingâ it under the actions:{} section of the js file. If you take onInsertFurniture out of the actions part of the js file, and instead, declare it as a normal function - it does get passed down, but then when you call it from a child component you lose the ability to call âthis.getâ inside it.
Thanks for pointing out the nested components methods, penny is dropping.
Iâve worked out the answer to my second problem through trial and error, you just tell the tag itâs an action. I didnât try it because I was thinking that would only work on an old class component.
Of you mean, just marking any actions as @action in the JS file would work ?
Yes, provided your component is a JS class and your function is a property of that class:
If youâre using Component.extends({ actions: { classicAction(){} } }), the @action decorator isnât going to work on classicAction.
If youâre using class MyComp extends Component { modernAction(){} }, @action on modernAction is all you need.
Whatâs really going on:
When you pass a function in the handlebar, it needs its this to be bound to your instance. @action does that to the member function when the class is constructed, and thatâs all it does. A standard object bind operation on init will do the same thing, but the syntax is messier.
The action modifier and helper in the handlebars do that, too â among several other things, and thatâs their problem. âMagicâ mechanisms that do several things at once seem to offer a promise of not having to think about the nuts and bolts. Then, when you have to think about how things work after all, the âmagicâ makes the actual behavior of code harder to reason about. This is why we recommend moving away from {{action}} at your leisure , but they still do what they always did.