Inheritable component layout frame?


#1

I want to build an abstract component which other components inherit from. I want that abstract component to provide a layout frame for the children components to render within. I thought I could accomplish this with layout and named templates but it seems like these constructs are inverted.

That is, I thought I’d be able to do layout: componentFrame in the abstract parent component and then have all the children components render with their own named templates.

Here’s a twiddle where I tried to accomplish this but was unable to.

Am I missing something or is this basically impossible?


#2

This is intentionally impossible. It used to work more like you’re imagining, and it caused bugs and confusion.

You can make a component that brings its template along when it is extended. That is what the default blueprint for components does in addons, and to get the same effect in your twiddle you would make component-frame.js and explicitly set the layout the same way you currently do in my-component.js. But that doesn’t allow you to put other templates inside the template – children can either keep it as is, or replace it entirely. There is not a way to mash them together.

But there is an easier thing to do, without the downsides. Composition over inheritance. Instead of extending component-frame, make the other components call it in their own template:

{{#component-frame}}
  Child A
{{/component-frame}}

This makes the “where the heck is this div coming from?” question non-mysterious, because it’s right there in the template. And the component-frame can yield values and actions, so you can tie behaviors in the component-frame to the markup in the child component.


#3

@ef4 you continue to be awesome, thank you for your thoughtful response!!

IMO it’s actually more confusing to require developers to manually add some required snippet to the component’s template.

If I build a component that is supposed to encapsulate something & be extended, then I’ve created a leaky abstraction if you need to both extend from my component & add some magic sprinkles to your template. For my specific use-case, I don’t even need to wrap the child components, I simply need to emit computed values above them.

Is there some way to emit a warning if the developer forgets to add the required snipped in their child component’s template?

What are some use cases for the (imo inverted) way it’s designed to work currently wrt layout vs named template & {{yield}}? Maybe this feature should simply be removed.


#4

If I build a component that is supposed to encapsulate something & be extended, then I’ve created a leaky abstraction if you need to both extend from my component & add some magic sprinkles to your template.

I agree, but I’m saying don’t do that. Don’t create APIs that involve extending components. They fail at actual encapsulation, because all your component’s internal details are exposed to the extending components.

I’m saying to make the in-template call to the component-frame the only form of composition between the components. No inheritance.

If you can share more specifics of your example, I could try to help further.


#5

After your reply I spent a couple hours trying to refactor into a self-contained component. I got kind of close but couldn’t quite drag it across the line. Then I got distracted with higher priority stuff.

If I may ask for a point of clarification:

because all your component’s internal details are exposed to the extending components.

Basically you’re saying to prefer composition over inheritance here right? Surely there are times when inheritance is appropriate. Right?

I’d be curious to understand what sort of “bugs and confusion” the layout frame caused. Obviously the way it works now caused confusion for me (& my colleague when I asked him about it). Surely there’s room for further improvement, though I’m not sure what that would look like yet … :thinking:

IMO, the word “layout” evokes the idea of a frame that will yield to a template (maybe just because of my Rails background?) I’d like it if layout worked as I expected but if the Ember team has already been down that road and deemed it confusing, maybe we should just remove the layout property altogether from Component?

Or maybe simply renaming layout to template would clarify things and then we could update the docs (scroll to h2#layouts) to simply indicate that the template can be assigned programmatically.


#6

Yes, though rather than make breaking changes to Ember.Component, we’re more likely to just not repeat this problem in the new glimmer component API.


#7

Sure, that’s reasonable. Thanks for following up! :smiley: