Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing props to Vue root instance via attributes on element the app is mounted on

Tags:

vue.js

vuejs3

I am terribly new to Vue, so forgive me if my terminology is off. I have a .NET Core MVC project with small, separate vue pages. On my current page, I return a view from the controller that just has:

@model long;

<div id="faq-category" v-bind:faqCategoryId="@Model"></div>

@section Scripts {
    <script src="~/scripts/js/faqCategory.js"></script>
}

Where I send in the id of the item this page will go grab and create the edit form for. faqCategory.js is the compiled vue app. I need to pass in the long parameter to the vue app on initialization, so it can go fetch the full object. I mount it with a main.ts like:

import { createApp } from 'vue'
import FaqCategoryPage from './FaqCategoryPage.vue'

createApp(FaqCategoryPage)
    .mount('#faq-category');

How can I get my faqCategoryId into my vue app to kick off the initialization and load the object? My v-bind attempt seems to not work - I have a @Prop(Number) readonly faqCategoryId: number = 0; on the vue component, but it is always 0.

My FaqCategoryPAge.vue script is simply:

<script lang="ts">

    import { Options, Vue } from "vue-class-component";
    import { Prop } from 'vue-property-decorator'
    import Card from "@/Card.vue";
    import axios from "axios";
    import FaqCategory from "../shared/FaqCategory";

    @Options({
        components: {
            Card,
        },
    })
    export default class FaqCategoryPage extends Vue {
        @Prop(Number) readonly faqCategoryId: number = 0;

        mounted() {
            console.log(this.faqCategoryId);
        }
    }

</script>
like image 551
Jonesopolis Avatar asked Sep 22 '20 13:09

Jonesopolis


People also ask

How do you pass the Vue prop?

To specify the type of prop you want to use in Vue, you will use an object instead of an array. You'll use the name of the property as the key of each property, and the type as the value. If the type of the data passed does not match the prop type, Vue sends an alert (in development mode) in the console with a warning.

What is $El in Vue?

The $el option in Vue provides Vue with an existing HTML element to mount the Vue instance generated using the new keyword. this. $el. querySelector is used to access HTML elements and modify the element's properties.

How do you access a root component from a child instance in Vue?

Conclusion. We can access the root Vue instance with $root , and the parent component with $parent . To access a child component from a parent, we can assign a ref with a name to the child component and then use this. $refs to access it.

How do I use props to pass data to child components in Vue?

The way it works is that you define your data on the parent component and give it a value, then you go to the child component that needs that data and pass the value to a prop attribute so the data becomes a property in the child component. You can use the root component (App.


2 Answers

It seems passing props to root instance vie attributes placed on element the app is mounting on is not supported

You can solve it using data- attributes easily

Vue 2

const mountEl = document.querySelector("#app");

new Vue({
  propsData: { ...mountEl.dataset },
  props: ["message"]
}).$mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app" data-message="Hello from HTML">
  {{ message }}
</div>

Vue 3

const mountEl = document.querySelector("#app");

Vue.createApp({  
  props: ["message"]
}, { ...mountEl.dataset }).mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0/vue.global.js"></script>
<div id="app" data-message="Hello from HTML">
  {{ message }}
</div>

Biggest disadvantage of this is that everything taken from data- attributes is a string so if your component expects something else (Number, Boolean etc) you need to make conversion yourself.

One more option of course is pushing your component one level down. As long as you use v-bind (:counter), proper JS type is passed into the component:

Vue.createApp({
  components: {
    MyComponent: {
      props: {
        message: String,
        counter: Number
      },
      template: '<div> {{ message }} (counter: {{ counter }}) </div>'
    }
  },
}).mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0/vue.global.js"></script>

<div id="app">
  <my-component :message="'Hello from HTML'" :counter="10" />
</div>

Just an idea (not a real problem)

Not really sure but it can be a problem with Props casing

HTML attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase. That means when you're using in-DOM templates, camelCased prop names need to use their kebab-cased (hyphen-delimited) equivalents

Try to change your MVC view into this:

<div id="faq-category" v-bind:faq-category-id="@Model"></div>
like image 55
Michal Levý Avatar answered Apr 29 '23 13:04

Michal Levý


Further to Michal Levý's answer regarding Vue 3, you can also implement that pattern with a Single File Component:

app.html

<div id="app" data-message="My Message"/>

app.js

import { createApp } from 'vue';
import MyComponent from './my-component.vue';
const mountEl = document.querySelector("#app");

Vue.createApp(MyComponent, { ...mountEl.dataset }).mount("#app");

my-component.vue

<template>
  {{ message }}
</template>

<script>
export default {
  props: {
    message: String
  }
};
</script>

Or you could even grab data from anywhere on the parent HTML page, eg:

app.html

<h1>My Message</h1>
<div id="app"/>

app.js

import { createApp } from 'vue';
import MyComponent from './my-component.vue';
const message = document.querySelector('h1').innerText;

Vue.createApp(MyComponent, { message }).mount("#app");

my-component.vue

<template>
  {{ message }}
</template>

<script>
export default {
  props: {
    message: String
  }
};
</script>

To answer TheStoryCoder's question: you would need to use a data prop. My answers above demonstrate how to pass a value from the parent DOM to the Vue app when it is mounted. If you wanted to then change the value of message after it was mounted, you would need to do something like this (I've called the data prop myMessage for clarity, but you could also just use the same prop name message):

<template>
  {{ myMessage }}
  <button @click="myMessage = 'foo'">Foo me</button>
</template>

<script>
export default {
  props: {
    message: String
  },
  data() {
    return {
      myMessage: this.message
    }
  }
};
</script>
like image 35
camslice Avatar answered Apr 29 '23 11:04

camslice