Vue: Allow children to "inherit" components registered for parents.

Created on 10 Sep 2015  ·  36Comments  ·  Source: vuejs/vue

I think this feature was removed intentionally, but in some cases it can be very useful. Why not to put this thing back probably making it optional?

Most helpful comment

Importing them explicitly is a worthwhile repetition. It allows you to look at any component in that hierarchy alone and understand where its dependencies come from. With implicit fallback, you won't remember where you imported those components in the hierarchy 3 months later.

All 36 comments

Any real world use case?

In small use cases, just register everything globally; in large apps, it's much more maintainable for each component to explicitly depend on what it needs. It maybe useful in some situations, but the benefits comes with a global tradeoff. The idea behind 1.0 is that "if something is only marginally useful, or has negative implications on maintainability, let's remove it."

Yep. My application has a popup window having a complex structure of custom editor elements. These elements can be combined in an 3-4 levels hierarchy, making it inefficient to declare them explicitly for each parent element (3-5 parent types * 10 declared elements = 50 lines of repeating code). And, it's also not good to register them globally as they'll never appear in other parts of the application. So I'd love to have them loaded "locally".

Importing them explicitly is a worthwhile repetition. It allows you to look at any component in that hierarchy alone and understand where its dependencies come from. With implicit fallback, you won't remember where you imported those components in the hierarchy 3 months later.

@yyx990803 I have a good memory, thanks. So I'll remember that my app consists of two very different pieces, each of them registering a well-defined set of components specific to it. So I would prefer to have a choice where to load my assets (and I suspect the same thing happened to custom directives and filters).

Let me share my impression. I worked with 0.12.x and it went VERY well (minus some minor learning curve things). Minimalistic and clean API, syntax, reliable code. Now, we're at 1.0.0-beta and it became WORSE, not better. More code repetition, features I use removed making me rewrite the same code over and over. I'm starting to think I made a mistake choosing Vue over React, because I am not sure there will be no more breaking changes and waste of my time in future.

@karevn

  1. It'd be much more helpful if you actually list the specific things that made the dev experience worse in addition to this issue.
  2. 1.0.0-alpha are pre-releases, which means there was no API stability guarantee in the first place. If you value stability, you should stick to 0.12 and wait for stable 1.0 (which will also have a final migration release). Using a pre-release means you have agreed to deal with constant breaking changes.
  3. 1.0.0-beta is not even released. It's probably not a good idea to use a non-released, work-in-progress branch.
  4. I'm designing the API based on my experience and feedback from the entire community. You are entitled to whatever you think, and feel free to switch to other frameworks if the changes are not in a direction that you like. (In fact, in React you also have to import everything explicitly, and will have to repeat even more stuff.)
  1. After reading the discussion #1170 I am almost agree now. But .. I really don't see a point in removing existing code that provided this feature instead of just making strict: true the default. Tastes differ, and some people will prefer to have a "fallback" approach, which is more intuitive sometimes. Especially, when components are loaded dynamically. It can be worked around with mixins, factories, etc, but it all takes precious time.
  2. Sure thing. But there's always a balance between the "ideal architecture" and cost of changes. In this case a few lines of code (namely: about 10) those would not hurt anyone if they were left cost me significant time. And I have to use this unstable version as I really need "read-write binding filters" feature those probably will not be backported to 0.12.x
  3. See 2.
  4. The question is not "which API do I prefer". I prefer Vue. Period. The question is "if I can rely on Vue API in long term". Reliability over handsomeness. If changes are breaking, there should be serious reason for them. In the case of the new binding syntax, which broke ALL of my code - okay, let it be, it's more readable and forces a better code structure. In this case - nope. This change might be non-breaking with options.strict = true set by default.

Yes, upgrading always come with the pain of refactoring, but 1.0 is the only chance for Vue to break free from these legacy config options. After 1.0 it will be strictly semver, and nothing should break until 2.0. And I want 1.x to last as long as possible, because of the reliability issue you talked about.

Regarding strict mode: it surely costs refactoring time when you've heavily relied on it - but ideally for new users who pick up Vue after 1.0, they don't even need to know this thing existed. The API surface should be as small as possible, and the global structuring pattern should be as consistent as possible. Making it possible to disable strict mode essentially encourages two different styles of structuring Vue apps - imagine people working on one app that uses strict: true, then moving to another project that uses strict: false... it creates fragmentation of developer experience, and I want to get rid of that possibility, and 1.0 is the only reasonable place to do that.

It's kind of unlucky for you to be caught in the middle of this transition, and I appreciate your feedback. But what needs to be done has to be done.

@yyx990803 I can see a concrete use case I'm kind of stuck with.

What I am trying to do

I build an extendable application: extandable with widgets. A widget is a developer defined piece of the application that plug in the global application to extend it at some points ; it is dynamically loaded at the startup of the application. Each instance of the application can have different set of widgets and 2 instances of the application can live on the same page.

When loaded the widget will add a dynamically created component to the vuejs application.
Widgets can contain other widgets (children)., we are not aware of how it will be at the startup because this part is managed by the user after application is loaded. That's why widgets need to be aware of each other and to register other widgets.

The problem

I want to avoid to register these widgets globally (compatibility reasons).
Because the components are built and loaded dynamically I need to register all components in each components that can contain children. That does a lot of registering that might not be used. See what I mean (please note that for the moment I didnt try local registration of components, making the tests with global registration):

var components = {

    appComponents: {
       template: "...",
       components: components
    },

    appComponents2: {
       template: "...",
       components: components
    },

    widgetComponents: {
       template: "...",
       components: components
    },

    widgetComponents2: {
       template: "...",
       components: components
    },

}

Is there a performance bottleneck when doing this?

That's why I think a "semi global" component's scope might be useful. It could help to build applications with a closed scope of component where components will be reachable from root and children components. But not from other vuejs roots. What do you think?

Global registration doesn't register recursively, it only registers the
component itself and not those inside it's components option. so I guess
there wasn't a problem.

On Mon, Aug 22, 2016, 16:00 Soufiane Ghzal [email protected] wrote:

@yyx990803 https://github.com/yyx990803 I can see a concrete use case
I'm kind of stuck with.

_What I am trying to do_

I build an extendable application: extandable with widgets. A widget is a
developer defined piece of the application, it is dynamically loaded at the
startup of the application. Each instance of the application can have
different set of widgets.

When loaded the widget will add a dynamically created component to the
vuejs application.
Widgets can contain other widgets (children)., we are not aware of how it
will be at the startup because this part is managed by the user after
application is loaded. That's why widgets need to be aware of each other.

_The problem_

I want to avoid to register these widgets globally.
Because widgets are loaded dynamically I need to register all widget in
each widget that can contain children. That does a lot of registering that
might not be used. See:

var components = {

appComponents: {
   template: "...",
   components: components
},

appComponents2: {
   template: "...",
   components: components
},

}

_Is there a performance bottleneck when doing this?_


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/vuejs/vue/issues/1297#issuecomment-241339596, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AFTLl6QDePtH93VOU2lgrC72Z0vKLsv-ks5qiVcXgaJpZM4F7M1v
.

@fnlctrl It's not about global registration.

The problem is that

  • global registration register for all vue+component instances.
  • local registration registers for current component only
  • and there is not way to register for root and children "semi global": something that allows to register for the current vue instance (including the components added into this instance too).

In my opinion the problem is that vue is though for the static (global) way, but it is restricted for the packaged/distributable (local) way.

semi-global registration is actually already possible since Vue has prototypal inheritance.
https://jsfiddle.net/fnlCtrl/32dt9e9g/

@fnlctrl
I'm not sure to understand what you want to show with the fiddle you sent (please note that your example has error: Unknown custom element: <bar> - did you register the component correctly?)

I'm not clear enough, maybe you didnt understand what I want to explain. Let's start again:

What I want to explain is that we can:

  • globaly register components with Vue.component('name', {...}) (that's perfect for single page app)
  • Locally register a component in a component new Vue({ components: {...} }); (that's good to ship components with dependencies for local reusability)

But we can't make available components from the parent to the children. Something like registering globally components for the current vm instance and all the components loaded in this instance, but not for omponents loaded in other vm instances. See the example: https://jsfiddle.net/p8wqafm1/2/

Do you understand?

Oops it seems that my fiddle wasn't saved correctly..
This is the one I intended to show you..
https://jsfiddle.net/fnlCtrl/32dt9e9g/1/

I'm reading your example right now.

I've forked your example here that is corrected to work, I hope I understood you correctly:
You want to add dynamic components limited inside Foo.

@fnlctrl Thank for your example but it seems it does not cover what I'm trying to achieve yet.

Using the method in your example registers component in Foo only, but that does not make available them in Foo's children (Bar in this example).

See the fiddle, I register Baz in Foo and I would like it to be available in Bar because it's loaded fromFoo: https://jsfiddle.net/8y0Lmb01/3/

Forked your example: https://jsfiddle.net/fnlCtrl/uvzaotaz/

The point is that components should have a clear dependency tree, and dynamic components depending on each other shouldn't be an exception.

@fnlctrl In you example Baz is not available in Foo anymore.

To do so I could use Vue.component('baz', {...} but the problem is that it will "pollute" other vue instance with this baz component.

OR

I could register Baz in both of Foo and Bar, and all foo children, and all bar children, and all Foo Grand Children, etc... But that adds a lot of complexity in case of large/dynamic app

Do you see what I mean? I can register locally, but we can't inherit components for the children, grand-children,... of the current components _only_

Yes I see your point now. Sorry that I wasn't aware that registering
component under Foo doesn't make it global inside Foo's scope, unlike
Vue.component . Will look into the source to see why.

On Mon, Aug 22, 2016, 20:17 Soufiane Ghzal [email protected] wrote:

@fnlctrl https://github.com/fnlctrl In you example Baz is not available
in Foo anymore.

To do so I could use Vue.component('baz', {...} but the problem is that
it will "pollute" other vue instance with this baz component.

OR

I could register Baz in both of Foo and Bar, and all foo children, and
all bar children, and all Foo Grand Children, etc... But that adds a lot of
complexity in case of large/dynamic app

Do you see what I mean? I can register locally, but we can't inherit
components for the children, grand-children,... of the current components
_only_


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/vuejs/vue/issues/1297#issuecomment-241395119, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AFTLl2ud0GDO_hOwFN8GIA1TzVEF1q0Fks5qiZM9gaJpZM4F7M1v
.

Thanks :)

The point is that I want to ship a standalone library that depends on vue, registering components for a given instances will be a real benefit, because anyway, as a standalone library, I'm not allowed to register that in the global Vue instance (that would break the standalone part).

Please, let me know if what I was talking about might be implmented in Vue

Well I guess that the reason was too obvious that I overlook: I wasn't
usingnew Foo()...

Will get a fiddle in 20min, on my ride home.

On Mon, Aug 22, 2016, 20:27 宋铄运 [email protected] wrote:

Yes I see your point now. Sorry that I wasn't aware that registering
component under Foo doesn't make it global inside Foo's scope, unlike
Vue.component . Will look into the source to see why.

On Mon, Aug 22, 2016, 20:17 Soufiane Ghzal [email protected]
wrote:

@fnlctrl https://github.com/fnlctrl In you example Baz is not
available in Foo anymore.

To do so I could use Vue.component('baz', {...} but the problem is that
it will "pollute" other vue instance with this baz component.

OR

I could register Baz in both of Foo and Bar, and all foo children, and
all bar children, and all Foo Grand Children, etc... But that adds a lot of
complexity in case of large/dynamic app

Do you see what I mean? I can register locally, but we can't inherit
components for the children, grand-children,... of the current components
_only_


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/vuejs/vue/issues/1297#issuecomment-241395119, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AFTLl2ud0GDO_hOwFN8GIA1TzVEF1q0Fks5qiZM9gaJpZM4F7M1v
.

Well, new Foo(...) didn't work as well: https://jsfiddle.net/8y0Lmb01/5/

Strange indeed... https://jsfiddle.net/fnlCtrl/p0ggkncu/
Looking into it now.

I've read some of the source code, and found that on Vue itself, we can hack like this:
https://jsfiddle.net/fnlCtrl/522aw9sm/
(without using Vue.extend or Vue.component, btw Vue.component is only a helper function that does Vue.extend and modifies Vue.options.components)

Though the same approach doesn't work on an extended Vue:
https://jsfiddle.net/fnlCtrl/v1m2s16u/

So I suppose the problem is caused by components resolution. I'll keep looking.

@fnlctrl Ok thanks, I tried to check some things and came up to the same conclusion as yours. I dont know the core enough to find why it works this way. Do we know if it is the expected behavior?

I think these comments about resolveAsset

Resolve an asset.
This function is used because child instances need access
to assets defined in its ancestor chain.

suggests that registering components on extended Vue is supposed to work.

The code in the function body does not look at the ancestor chain, right? Maybe it was not implemented yet?

I don't know enough yet, still learning its behavior, but I guess the "ancestor chain" refers to the first parameter options.

I guess I can conclude that the cause is this (src/core/global-api/extend).
It makes extended classes use the same method of their parents.

I've tested that, if you copy what's in core/global-api/assets (use the corresponding code inside dist version that is stripped of types, of course)
to vue.extend, to make it look like this (change Vue to Sub):

config._assetTypes.forEach(function (type) {
        Sub[type] = function (id, definition) {
          if (!definition) {
            return this.options[type + 's'][id];
          } else {
            /* istanbul ignore if */
            if ("development" !== 'production') {
              if (type === 'component' && config.isReservedTag(id)) {
                warn('Do not use built-in or reserved HTML elements as component ' + 'id: ' + id);
              }
            }
            if (type === 'component' && isPlainObject(definition)) {
              definition.name = definition.name || id;
              definition = Sub.extend(definition);
            }
            if (type === 'directive' && typeof definition === 'function') {
              definition = { bind: definition, update: definition };
            }
            this.options[type + 's'][id] = definition;
            return definition;
          }
        };
      });

the Foo = Vue.extend() and Foo.component() will work.

Though I guess this will cause some performance penalty.

@gsouf And I think I've found the last piece of the puzzle (an equivalent workaround without modifying vue):
https://jsfiddle.net/fnlCtrl/v1m2s16u/

var Root = Vue.extend()

Root.options.components.Foo = Root.extend({
    template: '<div>Foo</div>'
})

Root.options.components.Bar = Root.extend({
    template: '<div>Bar, uses <foo></foo></div>'
})

new Root({
    template: `
  <div>
    <foo></foo>
    <bar></bar>
  </div>
  `
}).$mount('#app')

Hi @fnlctrl, thanks for the output and sorry for the delay.

Indeed looks like comoponents inherit components from them constructor, not from them parent. Currently looking if I can apply a patch for my use case.

In your case it remains attached to a constructor, not to an instance, I'm looking for it to be part of the instance only

@fnlctrl due to the the way javascript works and thanks to your example I could workaround it by "extending dynamically" vue for each instance I create, making everything available for this application only.:

createVueInstance = function(el, data){
    var vExtend = Vue.extend();
    vExtend.partial('some-semiglobal-partial', "...");
    vExtend.component('some-semiglobal-component', vExtend.extend({...}));

    return new vExtend({
        el: el,
        data: data
    });
};

After checking how the core was built, it does not look the it was built to allow easy integration of per instance available components and this workaround is stable enough for me.

Thanks for your help!

Btw, I think the example you showed to me could be deeply explained in the doc. I didn't find a mention about that

@gsouf You're welcome. I thinks this issue is enough for those who want to implement a similar feature :smile:

Here I have a 'semi-global' use case:

I have a relatively universal Layout component, but the content is configurable by the components that use the Layout component, eg. component A use Layout and want to configure its content with component B, some other component may use Layout and configure its content with component C, etc.

Should this pattern be supported?

Or is there any solution to replace this design?

The pattern is widely used in iOS to improve code reuse and this is a flexible design.

@hpsoar What you probably need is slots

My Design is as follows, basically this will allow me to do two things with the cell:

  1. simple config the cell with a css style, which will sufficient for many cases;
  2. insert a component into the cell, which will be used for special cases.
<template>
  <div class="tile is-ancestor">
    <div class="tile is-parent">
      <article class="tile is-child box">
        <div class="table-responsive">
          <table class="table is-bordered is-striped is-narrow">
            <thead>
            <tr>
              <th v-for="c in columns">
                {{c.title}}
              </th>
            </tr>
            </thead>
            <tbody>
            <tr v-for="(item, index) in items">
              <template v-for="c in columns">
                <td v-if="c.hasOwnProperty('component')"><div :is="c.component"></div></td>
                <td v-else>{{ item[c.name] }}</td>
              </template>
            </tr>
            </tbody>
          </table>
        </div>
      </article>
    </div>
  </div>
</template>

<script>

export default {
  components: {
  },
  props: [
    'columns',
    'items'
  ],
  data: function () {
    return {
    }
  }
}

</script>

<style lang="scss" rel="stylesheet/scss">
  .table-responsive {
    display: block;
    width: 100%;
    min-height: .01%;
    overflow-x: auto;
  }
</style>

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bfis picture bfis  ·  3Comments

loki0609 picture loki0609  ·  3Comments

wufeng87 picture wufeng87  ·  3Comments

loki0609 picture loki0609  ·  3Comments

franciscolourenco picture franciscolourenco  ·  3Comments