0

How do I call a method on a component residing in a Vue instance?

There are several questions like this one on SO but they all seem to have answers that are either deprecated, under-explained or contain answers that are concluded with "but it's recommended you don't do this..."

So I thought by providing a specific use case scenario with a current version of Vue I could glean insight into Vue "best practice" when it comes to calling methods on reusable components (or if this in itself is an anti-pattern).

Scenario: Create a reusable Vue component that represents a table that updates its data based on an API url passed at creation. Upon certain events outside the Vue instance (for example the window loses and regains focus etc), the data should update.

The table template creates from columns and rows Arrays.

The reusable, live table component:

Vue.component('live-table', {
    props: ['url'],
    data: function () {
        return {
            rows: [],
            columns: []
        }
    },
    created () {
        this.setTable();
    },
    methods: {
        setTable () {
            axios
                .get(this.url, {timeout: 1000})
                .then(
                    response => {
                        this.rows = response.data.rows;
                        this.columns = response.data.columns;
                    }
                )
                .catch(error => (console.log(error)));
        },
    },
    template: `
        <table>
            <thead>
                <tr>
                    <th v-for="col in columns">
                        {{col}}
                    </th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="row in rows">
                    <td v-for="col in columns" v-html="row[col]"></td>
                </tr>
            </tbody>
        </table>`
});

Which would be used in the <body> like this:

<div id="app">
    <live-table url="/api/my_demo_data"></live-table>
</div>

Where the #app instance is:

var app = new Vue({
    el: '#app'
})

Adding this code sets up the table correctly with the data from the API. The setTable method in the 'live-table' component would update the data from the API.

How do I call the setTable method in the live-table components that would exist in the #app Vue instance?

Or, am I misusing/misunderstanding the use-cases of Vue components? If so, what is the best way to reuse components like the above to maintain the DRY principle, and be able to refresh the data in the tables?

Tom Malkin
  • 2,134
  • 2
  • 19
  • 35
  • The question presupposes that the component itself cannot call its own method when the conditions occur. Is this actually the case? Also did you try calling the method via a ref, as noted in the first answer to [this question](https://stackoverflow.com/questions/33682651)? – David Weldon Nov 25 '19 at 23:09
  • if a parent needs to call a method defined in a child, chances are you're not using Vue *correctly* – Bravo Nov 25 '19 at 23:14
  • @DavidWeldon "The question presupposes that the component itself cannot call its own method when the conditions occur. Is this actually the case?" - No idea. "did you try calling the method via a ref" - I did but it didn't work, but I also read another article (which I can't now find naturally) which suggested this was an antipattern and a misuse of components – Tom Malkin Nov 25 '19 at 23:35
  • @Bravo "chances are you're not using Vue _correctly_" - no doubt, that's why I'm asking this question – Tom Malkin Nov 25 '19 at 23:36

1 Answers1

0

There are several questions like this one on SO but they all seem to have answers that are either deprecated, under-explained or contain answers that are concluded with "but it's recommended you don't do this..."

The reason why people say not to do this is because most of the time it's an XY problem of which even this question may be.

Since you searched this already, I take it you know about refs, but if not then you can use <live-table ref="table"> to get the component's instance and then you can call the method on that instance as usual like this.$refs.table.setTable().

If you have many instances then you have to keep track of each table with ref and then remember to update them all. Alternatively use can use a global event bus which each live table instance can listen to, then you just need to emit a single event on the bus to instruct all the live tables to update.

But this can get messy. One the one hand, you've designed the component to handle the data and fetching operations all by itself, but then there are some situations where it needs to be manually told to update itself. Maybe it would be better to hoist the data fetching operation to the parent component so the parent "owns" the data and all it does is pass down the table data via props. If you need to update the data manually, then the parent takes care of that and you don't need to poke each child component.

These are just suggestions; I'm not saying any solution in particular is optimal, but it's something to consider.

Decade Moon
  • 32,968
  • 8
  • 81
  • 101
  • Yeah definitely felt like this could have been an XY problem, which is why I specified the scenario: create a reusable piece of code that represents a updating table that maintains DRY. Moving the method to the parent and passing through props sounds like a great idea - I didn't know props kept the data up to date in the child component. – Tom Malkin Nov 25 '19 at 23:40