Jonesopolis
Jonesopolis

Reputation: 25370

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

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>

Upvotes: 16

Views: 26188

Answers (6)

jebawi3853
jebawi3853

Reputation: 1

In the loaded component u can use the same props and in the beforeMount() function set the following:

this.$root.$props.{prop_name} = this.{prop_name}

i.e.:

props: [
    'data'
],
beforeMount() {
    this.$root.$props.data = this.data;
}

Upvotes: 0

Navid Shad
Navid Shad

Reputation: 1016

All other answers might be valid, but for Vue 3 the simple way is here:

import {createApp} from 'vue'

import rootComponent from './app.vue'

let rootProps = {};

createApp(rootComponent, rootProps)
   .mount('#somewhere')

Upvotes: 4

camslice
camslice

Reputation: 635

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>

Upvotes: 17

Michal Lev&#253;
Michal Lev&#253;

Reputation: 37763

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>

Upvotes: 37

Daniel
Daniel

Reputation: 35684

So I'm not at all familiar with .NET and what model does, but Vue will treat the DOM element as a placeholder only and it does not extend to it the same functionality as the components within the app have.

so v-bind is not going to work, even without the value being reactive, the option is not there to do it.

you could try a hack to access the value and assign to a data such as...

const app = Vue.createApp({
  data(){
        return {
        faqCategoryId: null
    }
  },
  mounted() {
    const props = ["faqCategoryId"]
    const el = this.$el.parentElement;
    props.forEach((key) => {
      const val = el.getAttribute(key);
      if(val !== null) this[key] = (val);
    })
  }
})

app.mount('#app')
<script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>

<div id="app" faqCategoryId="12">
  <h1>Faq Category Id: {{faqCategoryId}}</h1>
</div>

where you get the value from the html dom element, and assign to a data. The reason I'm suggesting data instead of props is that props are setup to be write only, so you wouldn't be able to override them, so instead I've used a variable props to define the props to look for in the dom element.


Another option

is to use inject/provide

it's easier to just use js to provide the variable, but assuming you want to use this in an mvc framework, so that it is managed through the view only. In addition, you can make it simpler by picking the exact attributes you want to pass to the application, but this provides a better "framework" for reuse.

const mount = ($el) => {
  const app = Vue.createApp({
    inject: {
      faqCategoryId: {
        default: 'optional'
      },
    },
  })

  const el = document.querySelector($el)

  Object.keys(app._component.inject).forEach(key => {
    if (el.getAttribute(key) !== null) {
      app.provide(key, el.getAttribute(key))
    }
  })

  app.mount('#app')
}

mount('#app')
<script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>

<div id="app" faqCategoryId="66">
  <h1>Faq Category Id: {{faqCategoryId}}</h1>
</div>

Upvotes: 2

Boussadjra Brahim
Boussadjra Brahim

Reputation: 1

As i tried in the following example

https://codepen.io/boussadjra/pen/vYGvXvq

you could do :

mounted() {
 console.log(this.$el.parentElement.getAttribute("faqCategoryId"));
}

Upvotes: 1

Related Questions