Jerry
Jerry

Reputation: 3608

Ember passing an action closure through an outlet

I am building a simple Ember app, but I have run into difficulty passing an action closure to a child component when that component is rendered in the {{outlet}} of a navigable container.

For context, here is a quick look at the aesthetically-astonishing app I have been building:

I have a roles/role path that displays a component (the yellow section above) with the following markup. Note that the model for this component is an instance of a Role:

// file: app/components/role.hbs

<p>{{@role.name}}</p>

<div>
  {{sel-nav-tabs items=this.tabConfig}}
  <div class='route-content'>{{outlet}}</div>
</div>

(Where "sel" stands for "someone else's library".)

this.tabConfig is defines in the corresponding class:

// file: app/components.role.js

import Component from '@glimmer/component';

export default class RoleComponent extends Component {

  get tabConfig() {

    return [
      { label: 'Users', route: 'roles.role.users' },
      { label: 'Privileges', route: 'roles.role.privileges' },
    ];

  }

}

Into the outlet in role.hbs will be rendered the appropriate list component, either users or privileges.

The users list is rendered by the following component. Note that the model is the list of User instances associated with the Role from its parent:

// file: app/components/role/user-list.hbs

<ul>
  {{#each @users as |user|}}
    <li>
      {{user.name}}
      {{#sel-button type="toolbar" onActivate=this.removeUser}}
        {{sel-icon/remove-circle}}
      {{/sel-button}}
    </li>
  {{/each}}
</ul>

and when the button is clicked it calls an action defined in the RoleUserListComponent class:

// file: app/components/role/user-list.js

import Component from '@glimmer/component';
import { action } from "@ember/object";

export default class RoleUserListComponent extends Component {

  @action removeUser(user) {
     // remove the user model from the role... but which role?
  }
}

The catch is that the relationship between users and roles is many-to-many, so I can't simply unset the user's owner and let Ember Data take care of things. The obvious answer seemed like passing an action closure from the role component to its child user-list component.

Except, there seems to be no way to pass the action closure through the {{outlet}}. What I was hoping for was something like:

{{outlet onActivate=(action removeUser @role)}}

which would pass the closure to any component that was rendered there. I tried instead to use {{yield user}} in the child to let the parent render the delete button and give it the appropriate action, but that also hit the outlet wall.

I also tried to use controllers, which aren't documented that well, probably since their role seems to have been evolving dramatically over Ember's maturation. But while this brief explanation does mention passing down actions, it doesn't go into details, and the few up-to-date examples I found all seem to break when an outlet joins the party.

I'm suspecting that {{outlet}} just plain isn't closure-friendly.

While defining a service would probably work, that doesn't seem to be what services are intended for, and I'd be cluttering up my global space to solve a local problem.

What is the best practice (or, really, any practice) for dealing with getting messages through outlets? I looked for ways to query the earlier parts of the path, but I didn't find any that were defined in the relevant classes.

EDIT to add more detail:

The route template for /roles/role is simply:

// file app/templates/roles/role

{{role role=@model}}

Where the Role component is in the first listing above. (I also added the role.js file contents above.) My reasoning for doing that was that by making a component I created a logical place to put the config (rather than inline helper functions) and it just gave me a sense of tidiness to have all ui elements be in components.

If a refactor can be the anchor to a good solution (essentially copying the entire Role component into the route template), however, I'll happily do it.

Upvotes: 1

Views: 657

Answers (1)

locks
locks

Reputation: 6577

{{outlet}} only supports one optional string argument for a named outlet and nothing else, so you won't be able to achieve this through the use of {{outlet}}!

Upvotes: 2

Related Questions