11

We know it possible to register global Vue.js (version 1) components dynamically like this:

Vue.component('my-component', MyComponent)

Is there a way to do the same for local components, i.e. those available/visible only to a specific component?

The idea is that I want to be able to pass components to be locally registered like this:

<outer-component
  :local-component-to-be-registered-dymanically="Foo"
>

So that in the outer-component I could then do:

created() {
    this.someMethodINeedToRegisterLocalComponent('cool-component', this.localComponentToBeRegisteredDynamically);
},

5 Answers 5

8

This is how I ended up importing and registering components dynamically to a component locally:

created() {
  const requireComponent = require.context(
    // The relative path of the components folder
    "PATH/TO/COMPONENTS_FOLDER",
    // Whether or not to look in subfolders
    false,
    // The regular expression used to match base component filenames
    /[A-Z]\w+\.(vue|js)$/
  );

  requireComponent.keys().forEach(fileName => {
    const componentConfig = requireComponent(fileName);
    console.log(componentConfig.default);
    // Gets the file name regardless of folder depth
    const componentName = fileName
      .split("/")
      .pop()
      .split(".")[0];
    // register the component locally
    this.$options.components[componentName] = componentConfig.default;
  });
}

Please notice that the components have to be registered before the component is mounted, the created life-cycle hook can be perfect for that.

See also the docs for Automatic Global Registration of Base Components.

Sign up to request clarification or add additional context in comments.

Comments

3

This is how you implement a dynamic component, not how to register a dynamic component. In .vue files you can only register a single component, any others would need to be defined externally.

You should be able to achieve what you're looking to accomplish using the :is attribute. Here's the example from the docs:

var vm = new Vue({
    el: '#example',
    data: {
        currentView: 'home'
    },
    components: {
        home: { /* ... */ },
        posts: { /* ... */ },
        archive: { /* ... */ }
    }
})
<component v-bind:is="currentView">
    <!-- component changes when vm.currentView changes! -->
</component>

or an alternative, where you bind directly to the component object (instead of using some data as a pointer):

var Home = {
    template: '<p>Welcome home!</p>'
}
var vm = new Vue({
    el: '#example',
    data: {
        currentView: Home
    }
})

4 Comments

Is there also a way to dynamically make vue aware of what components it could fill in there? I mean the part that currently is in the instantiation of Vue: components: { ...}
@gwildu's question seems more important here to me. Is it possible to created the components object dynamically?
actually the last part is what I have always wondered why it wasn't in Vue this is great. Thanks man.
This doesn't answer the OP's question: "Register [a] local Vue.js component dynamically", not How to use dynamic components. His question is: how can we register a component locally without making it global? (which is what Vue.component(...) does)
1

As of Vue 2.6, this cannot be done. Dynamic registration can only be done globally, using Vue.component.

Comments

0

I can't comment yet, but I have an addition to Shaya Ulmans answer:

Using Vue 2.6(.10): It does not need to be in the created hook if you check if the component has been registered already. If you don't and for example add it in the mounted hook or a custom method, it can not find the component if you did not register it yet.

This can be solved with a simple v-if statement.

I created a class based on Shaya Ulmans answer as a trial that can register a component locally to a given component using a config and dynamic imports. It could be done using a mixin, but I only require dynamic local registration in a subset of my components.

/**
 * @class dynamicLocalComponentRegistrator
 * @classdesc can register components to a given component locally
 */
export class dynamicLocalComponentRegistrator {
    /**
     * @typedef ComponentConfig
     * @type {object}
     * @property {string} name
     * @property {Function} component
     */

    /**
     * Reference to component the components should be locally registered to
     * @type {VueComponent}
     */
    localComponent;

    /**
     * Keep track of registered components
     * @type {string[]}
     */
    registeredComponents = [];


    constructor (localComponent) {
        this.localComponent = localComponent;
    }

    /**
     * Register multiple components, skipping those that are already registered
     *
     * @param {ComponentConfig[]} componentConfigArray
     */
    registerMultipleComponents (componentConfigArray) {
        componentConfigArray.forEach(componentConfig => {
            if (!this.isRegistered(componentConfig.name)) {
                this.registerComponent(componentConfig);
            }
        })
    }

    /**
     * Register a component locally to the local component
     *
     * @param {ComponentConfig} componentConfig
     */
    registerComponent (componentConfig) {
        if (!componentConfig.hasOwnProperty('name') || !componentConfig.hasOwnProperty('component') || typeof componentConfig.component !== 'function') {
            throw 'cannot register component: invalid config'
        }

        componentConfig.component().then(component => {
            this.localComponent.$options.components[componentConfig.name] = component.default;
            this.registeredComponents.push(componentConfig.name);
        });
    }

    /**
     * Shorthand to check registration
     *
     * @param {string} componentName
     * @returns {boolean}
     */
    isRegistered(componentName) {
        return this.registeredComponents.includes(componentName);
    }
}

Just import this in the components you need to have the functionality in. Then, wherever you like, create an instance and give this to the constructor.

For example:

mounted() {
        this.componentRegistrator = new dynamicLocalComponentRegistrator(this);
        this.componentRegistrator.registerMultipleComponents(this.customComponents);
    }

And to use it in a .vue template:

<component v-if="componentRegistrator && componentRegistrator.isRegistered(dynamicComponent.name)" :is="dynamicComponent.name" />

This does not throw errors and so far works beautifully.

Comments

-3

You don't do that. It's anti-pattern. Components are designed to only expose that many events and props to the parent components using it, you can leverage versatility and complexity and add more to the child's interfaces (props and events), to cover all the situations you want the child to adapt to. But not defining the child dynamically. If you're changing the child in the parent, then the component system's purpose of decoupling is disobeyed. If you want to change it in the parent, just make it part of the parent.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.