DEV Community

Cover image for Dynamic components using VueJS

Dynamic components using VueJS

Carlos Rodrigues on July 26, 2019

First things first, if you're just starting with VueJS this might be a bit too advanced for you, I would strongly recommend read documentation on V...
Collapse
 
lainpavot profile image
Lain • Edited

Hello!
Thanks for your article.
I still have one question:
When you do

<component v-bind:is="something"></component>

If "something" contains a bad value (non-existing component), it doesn't throws any error.
How can we handle this case?

To give you a better background:

We have an SPA, and the user can modify the location hash to access articles:

http://mysite.com/index.html#super-article

This will load the async component "super-article", and show it via the "component" tag. I'd like to handle the case when a user enters a bad value (this is not usual, but anyway... Want to handle this bad case).

Do you know how to do that? (I don't like the "white list" solution...)

Collapse
 
pikax profile image
Carlos Rodrigues

Hi Lain,

You welcome :)

It will depend if the something is an lazy loaded component or component definition object.

In your case I will reckon you fetch the article from the database and then render, in that case if the response of the api is empty or 404 you can render a fallback component.

Collapse
 
lainpavot profile image
Lain • Edited

Thank you for your quick response!

I was hoping for something like <div v-except-nonexists> or something like that.

In my case, I have no way to trigger any 404 error, because each article's component is registered using it's metadata (name, tags, ...), and then it's loaded lazily only when my <component> tag asks for it. But Vue does nothing at all if the given component does not exists.

I'll keep my whitelist solution because the solution you provides implies to rewrite the whole loading system: I register a (lazy) component from each article, keep track of all registered articles, and if the article referenced by the location hash has never been registered, I show an error with v-if/v-else:

  <component v-if="loaded_articles[curent_page]" :is="curent_page"></component>
  <div v-else class="article article-content">
    <previous-button></previous-button>
    <h3>{{curent_page}}</h3>
    <p>{{$t("articles.article_not_found")}}</p>
  </div>

This method prevents any additional request to the server...

Thread Thread
 
pikax profile image
Carlos Rodrigues

It if you loading through webpack, it's simple, because import will throw an exception.

What do you mean by:

Vue does nothing at all if the given component does not exists.

You should be able to add that condition to v-if, no?

Are you registering the components using Vue.component()?

Thread Thread
 
lainpavot profile image
Lain

No, I don't use webpack.

Yes, I use Vue.component(name, promise) to register my components.

When my Vue instance is mounted, I set the language, and it triggers the loading of corresponding articles' components. Here is a simplified version of my code:

    function load_article(article, header, err_code) {
        // here, "article" is the raw html of my article
        // We'll insert it into the article's template
        return {
            data: {// some data...
            },
            template: `a template ${article} that encapsulate each article`
        }
    }
    function loader (header) {
        return (resolve, reject) => {
            vm.make_promise({
                url: `/article/${lang}/${header.name}.html`
            })
            .then((data) => {
                resolve(load_article(data, header, 200))
            })
            .catch((data) => {
                resolve(load_article(data, header))
            });
        }
    }
    var lang = vm.lang;
    headers = articles_headers();
    var loaded_articles = {};
    for(var i in headers) {
        header = headers[i];
        Vue.component(`${lang}-${header.name}`, loader(header));
        loaded_articles[`${lang}-${header.name}`] = true;
    }
    return loaded_articles;

And then, I have the code I wrote in my previous comment, with "curent_page" being the name of the article.

But, if we provide an article name that does not exists (and so, a component that is not registered), Vue shows a warning in the console, and that's all: [Vue warn]: Unknown custom element ....

Perhaps there's a way to tell Vue to call a given function when this happens?

Thread Thread
 
pikax profile image
Carlos Rodrigues

you should be able to check if the component is register by accessing Vue.options.components

Collapse
 
akselsoft profile image
Andrew MacNeill

How does this work when your components are in separate vue files? We don't want the component to be custom written by users but we still need to dynamically load the components (by name) via the JSON file.

So our API returns back a list of components to load (these would likely be part of the Webpack result) but that list is dynamic based on the API.

Collapse
 
pikax profile image
Carlos Rodrigues

If you have a component definition, you can have a component that loads the component from the API, I would use eval to run the javascript (using the same logic as in On-the-fly components Example).

Just be careful, using eval or new Function is usually not a good approach since it will run an possible unsecure javascript and slow.

Collapse
 
akselsoft profile image
Andrew MacNeill • Edited

Thanks Carlos - security is definitely a concern - I don't want to be loading javascript on the fly.

I think the problem is that my components aren't being registered globally properly.

I thought by loading the components in the parent component, they would be available to the lower level components but they aren't.

So in my page component, I'm doing the import (but not using the component). Shouldn't that component be available to the child?

--- EDITED ---
I'm using Quasar as our framework and they don't use a main.js but a plug-in approach. So that's where I have to go.

Thanks

Thread Thread
 
pikax profile image
Carlos Rodrigues

I think the problem is that my components aren't being registered globally properly.

You can use Vue.component() to register globally.

Never used Quasar, if you can create a link on codesandbox with an sample on what you want to do, I can have a look and try to help.

Thread Thread
 
akselsoft profile image
Andrew MacNeill

No need - I've got it working now - I'm fairly new to vue but your examples set me on the right path.

quasar lets you build separate plug-ins.

I'll post an article when I've got it working smoothly.

Collapse
 
rezaerfani67 profile image
RezaErfani67

this is excellent article
thank you alot