Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: consistent API between classic and Glimmer components #563

Closed
boris-petrov opened this issue Dec 17, 2019 · 11 comments
Closed

Comments

@boris-petrov
Copy link

... for creation and rendering of such templates.

This is provoked from this Discord discussion.

The main point is as follows. I have the following code currently (Ember 3.15):

import Component from '@ember/component';
import { createTemplateFactory } from '@ember/template-factory';

...

const component = Component.extend({ tagName: '', layout: createTemplateFactory(precompiledTemplate) });
getOwner(this).lookup('application:main').register(componentName, component);

...

const component = getOwner(this).factoryFor(componentName).create();
component.on('didRender', function() {
  const html = this.element.innerHTML;
  ...
});
component.appendTo('...');

It dynamically creates a classic component, registers it, instantiates it and the instance is rendered on the page to extract some info from it.

In order to achieve the same result with a Glimmer component, the first part of the code could be translated like so:

import { setComponentTemplate } from '@ember/component';
import { createTemplateFactory } from '@ember/template-factory';
import GlimmerComponent from '@glimmer/component';

const CustomComponent = class extends GlimmerComponent {};
setComponentTemplate(createTemplateFactory(precompiledTemplate), CustomComponent);
getOwner(this).lookup('application:main').register(componentName, CustomComponent);

However the other parts have no Glimmer counterparts. I would have expected at least getOwner(this).factoryFor(componentName).create() to work fine but it blows up with Failed to create an instance of 'component:some-component'. Most likely an improperly defined class or an invalid module export..

Having a unified JavaScript API for component creation and rendering for any kind of component (classic, Glimmer, template-only Glimmer) would be nice. Not sure if it makes sense or is something Ember wants to invest in, but I just wanted to put this here so a discussion can be started.

CC @Gaurav0

@NullVoxPopuli
Copy link
Contributor

personally I don't think glimmer componets should allow this.

This could be the sort of thing LifeCyclecomponent solves

Glimmer is intended to be the lightweight, covers the 95% of use cases

I wouldn't want added callbacks and LifeCycle hooks forced on everyone

https://github.com/NullVoxPopuli/ember-lifecycle-component

@Gaurav0
Copy link
Contributor

Gaurav0 commented Dec 17, 2019

Here's some context:

We allow users to write their own HBS templates which are used in different places. One such place is the browser's title for example. There, obviously, only text is accepted. So I need a mechanism to get from an HBS template to a string of text (of course by passing some context data to the HBS template). Right now I do that by creating a classic component, instantiating it, rendering it and getting the text (all in JavaScript). I want to do the same with a Glimmer component for performance reasons. Not possible right now I think.

@Gaurav0
Copy link
Contributor

Gaurav0 commented Dec 17, 2019

@NullVoxPopuli He doesn't need callbacks or lifecycle hooks.

@Gaurav0
Copy link
Contributor

Gaurav0 commented Dec 18, 2019

dev-rfc discussion

@pzuraq
Copy link
Contributor

pzuraq commented Dec 18, 2019

I'd like to dig into the use cases here so we can get more details, but it sounded like most of the use cases could be solved if {{#in-element}} worked on elements that were not attached to the DOM. I'm not sure if it does, but I think it would, and this would allow the user to render whatever they wanted into a fragment without ever actually attaching it to the document, and then do what they wanted with the output.

@rwjblue
Copy link
Member

rwjblue commented Dec 20, 2019

I agree with @pzuraq, in-element is the right fit here. Also, just to clarify, in-element does work properly even when the target element is not in the DOM.

@ivp-dev
Copy link

ivp-dev commented Nov 27, 2020

If talks about use cases, for example, I have wrote an addon with itemscontrol (like in wpf), where component (item container) should be created in code, in response to itemcollection changing. Now I have to create model of component and render it in template. I would like to create and initialize component in code and manage component lifecycle by myself.

@pzuraq
Copy link
Contributor

pzuraq commented Nov 28, 2020

I think after learning more about the Glimmer rendering cycle while implementing strict mode, I'm more confident that this is something we do not generally want to support or allow to be a common pattern. There is a lot of overhead that comes with multiple rendering roots, both actual overhead of booting up another VM and running it, and conceptual overhead of managing a render root manually and not using templates.

So if we do want to add this capability, we need to not only show that it could be useful to some use cases, but that it would be essential to those use cases. That is, there would be no other reasonable way to accomplish these use cases that to have the ability to render multiple roots in a single application, manually.

@ivp-dev I understand how your example works, but can you provide more of explanation about why you architected it the way you did? Why, for instance, could you not have used contextual components and the {{component}} keyword instead? Why is programmatic creation essential here?

@ivp-dev
Copy link

ivp-dev commented Nov 28, 2020

Thank you for the answer. ItemsControl the base class for all components which should take care about multiple child elements. We can pass items to the component as a collection and render it internally in template

<TabControl @itemsSource="{{array}}"/>

or declare and pass it as a content and render with yield.

<TabControl as |Tabs|>
 <Tabs.Tab @header="Head">Some content</Tabs.Tab>
</TabControl >

I want to support both of these cases. In the first case I can wrap item in Model class and add some functionality (for example extend it with some properties, such as isSelected property or events) and render it with {{component}}. In the second case the component itself is the data passed as a child and I need to add it to the internal collection to support some features, for example to support selection or filtering. I solved it through the render-modifier. When the child component rendered I pass class instance of the child component to the internal collection of the itemscontrol and now I have an access to properties and can support selection or filtering. The problem that I would solve is that I need to support two different classes with the same features. Model class for itemsSource and Component class. Component could be used as data item. Model class will no need if I could create component manually.

@wagenet
Copy link
Member

wagenet commented Jul 23, 2022

I'm closing this due to inactivity. This doesn't mean that the idea presented here is invalid, but that, unfortunately, nobody has taken the effort to spearhead it and bring it to completion. Please feel free to advocate for it if you believe that this is still worth pursuing. Thanks!

@wagenet wagenet closed this as completed Jul 23, 2022
@NullVoxPopuli
Copy link
Contributor

I think this RFC solves the needs here? #813 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants