vuejs 3 data options property no more reactive with external object - vue.js

I have an app that shares an external module with other applications by sharing a global javascript object.
One of these apps is developed with vue 2 and when the global object is updated in the external module, the option data property of vue 2 is updated perfectly while in vue 3 it is not. I also tried with the new reactive property but nothing to do, is it a bug?
Not being able to make any changes to the external module because it is shared with other apps, how can I make it work in vue 3?
Here are some test links:
Vue 2 share external object
<script src="https://unpkg.com/vue"></script>
<script>
var EXTERNAL_OBJECT={
name:"Bob",
list:[{name:"Ivan"}]
}
function change_object(){
EXTERNAL_OBJECT.name+="+++"
EXTERNAL_OBJECT.list.push({name:"Carl"})
}
</script>
<button onClick="change_object()">change external object</button>
<div id="app">
<div>
{{share.name}}
</div>
<div v-for="item in share.list">
{{item.name}}
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
share:EXTERNAL_OBJECT
}
})
</script>
Vue 3 share external object
<script src="https://unpkg.com/vue#3.2.4/dist/vue.global.js"></script>
<script>
var EXTERNAL_OBJECT={
name:"Bob",
list:[{name:"Ivan"}]
}
function change_object(){
EXTERNAL_OBJECT.name+="+++"
EXTERNAL_OBJECT.list.push({name:"Carl"})
}
</script>
<button onClick="change_object()">change external object</button>
<div id="app">
<div>
{{share.name}}
</div>
<div v-for="item in share.list">
{{item.name}}
</div>
</div>
<script>
const app = Vue.createApp({
data () {
return {
share:EXTERNAL_OBJECT
}
}
});
app.mount('#app')
</script>
Vue 3 share external object with reactive property
<script src="https://unpkg.com/vue#3.2.4/dist/vue.global.js"></script>
<script>
var EXTERNAL_OBJECT={
name:"Bob",
list:[{name:"Ivan"}]
}
function change_object(){
EXTERNAL_OBJECT.name+="+++"
EXTERNAL_OBJECT.list.push({name:"Carl"})
}
</script>
<button onClick="change_object()">change external object</button>
<div id="app">
<div>
{{share.name}}
</div>
<div v-for="item in share.list">
{{item.name}}
</div>
</div>
<script>
const { createApp, reactive } = Vue
const app = createApp({
setup(){
let share = reactive(EXTERNAL_OBJECT)
return {
share
}
},
data () {
return {
msg:"reactive test"
}
}
});
app.mount('#app')
</script>
thanks

It is a bit hard to read ... I just look at the Vue3 Example.
How many file are you showing?
Cant write your EXTERNAL_OBJECT directly in the reactive property? Like:
const EXTERNAL_OBJECT = reactive({ name:"Bob",
list:[{name:"Ivan"}] });

Related

vuejs not showing the value inside {{ }} , the text is displayed as it is

I developed a vuejs program and run the npx serve command. I go to the directory and run npx serve. I browse the http://localhost:300/myvuefile.html , I get the following output only.
{{count}} inside the button. My code :
<script src="https://unpkg.com/vue#3/dist/vue.global.js"></script>
<div id="app">
<button #click="count++">{{ count }}</button>
</div>
<script>
import { createApp } from 'vue'
const app = createApp({
data() {
return {
count: 0
}
}
})
app.mount('#app')
</script>
I have to get the value of the count and onclick the count should increment. Instead I get the output as {{count}} only. Please help to fix this issue.
According to the documentation-
When using the global build of Vue, all top-level APIs
are exposed as properties on the global Vue object.
So, either use like this-
const app = Vue.createApp({
data() {
return {
count: 0
}
}
})
app.mount('#app')
<div id="app">
<button #click="count++">{{ count }}</button>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.js"></script>
Or use like this-
const { createApp } = Vue
const app = createApp({
data() {
return {
count: 0
}
}
})
app.mount('#app')
<div id="app">
<button #click="count++">{{ count }}</button>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.js"></script>

How to use template refs in Nuxt 3

In Nuxt2 there were template $refs that you could access in <script> with this.$refs
I would like to know what is the Nuxt3 equivalent of this is.
I need this to access the innerText of an element. I am not allowed to use querySelector or getElementById etc.
This is the way we write code. I can give html elements ref="fooBar" but I can't access it with this.$refs.fooBar or even this.$refs.
<script setup lang="ts">
import { ref, computed } from 'vue';
const foo = ref('bar');
function fooBar() {
//Do stuff
}
</script>
<template>
//Html here
</template>
With Options API
<script>
export default {
mounted() {
console.log('input', this.$refs['my-cool-div'])
}
}
</script>
<template>
<div ref="my-cool-div">
hello there
</div>
</template>
With Composition API
<script setup>
const myCoolDiv = ref(null)
const clickMe = () => console.log(myCoolDiv)
</script>
<template>
<button #click="clickMe">show me the ref</button>
<div ref="myCoolDiv">
hello there
</div>
</template>

Vue3 Components in a Separate File

I have an app that I am porting from Vue2 to Vue3. I am not using a bundler as Vue is just providing simple interactivity on a page. This is the simplified version in Vue2:
// my-modal.js
var myModal = Vue.component('my-modal',
{
template:
`<div id="myModal">
<button #click.prevent="open=true">Open Modal</button>
<div v-if="open" class="my-modal">
<p>Hello from the modal!</p>
<button #click.prevent="open=false">Close</button>
</div>
</div>`,
data() {
return {
open: false
}
}
})
In Vue 2 this Works;
<script src="https://unpkg.com/vue#2"></script>
<div id="app">
<div class="outer">
<h3>Modal Example - {{ message }}</h3>
<div>
<my-modal />
</div>
</div>
</div>
<script src="/js/my-modal.js"></script>
<script>
const app = new Vue(
{
el: '#app',
data() {
return {
message: 'Hello Vue!'
}
}
});
</script>
For Vue3, per the documentation at: https://v3-migration.vuejs.org/breaking-changes/global-api.html#a-new-global-api-createapp I switched things over to what I expected would work:
<script src="https://unpkg.com/vue#3"></script>
<div id="app">
<div class="outer">
<h3>Modal Example - {{ message }}</h3>
<div>
<my-modal />
</div>
</div>
</div>
<script src="/js/my-modal.js"></script>
<script>
Vue.createApp({
data()
{
return {
message: 'Hello Vue!'
}
}
}).mount('#app')
</script>
and in the my-modal.js file I changed the first few lines to use the Global Vue:
const { createApp } = Vue;
const app = createApp({});
app.component('my-modal', ...
The Vue instance works but the component is not found with the error message: "Failed to resolve component: my-modal". I tried adding a 'components' section to the Vue instance and a few other things with no luck.
Any suggestions?
Each instance from createApp() is unique. That is, calling createApp() in index.html does not return the same instance from the previous call in my-modal.js.
One solution is to declare the global app instance before importing my-modal.js:
<!-- index.html -->
<script>
window._app = Vue.createApp({⋯})
</script>
<script src="/js/my-modal.js"></script>
<script>
// finally mount
window._app.mount('#app')
</script>
// js/my-modal.js
window._app.component('my-modal', {⋯})
demo

Vue 3, $emit never firing

The documentation is not enough to be able to do the emit. I have seen many tutorials and nothing works, now I am testing this
Child component
<div #click="$emit('sendjob', Job )"></div>
With the Vue DevTools plugin I can see that the data is sent in the PayLoad, but I can't find a way to receive this emit from the other component.
Many people do this
Any other component
<template>
<div #sendjob="doSomething"></div>
</template>
<script>
export default {
methods:{
doSomething(){
console.log('It works')
}
}
}
</script>
In my case it doesn't work
You should import the child component in the parent component and use it instead of the regular div tag.
I'm sharing examples for your reference to achieve emits in Vue 3 using <script setup> and Composition API. I strongly suggest going with <script setup if you are going to use Composition API in Single File Component. However, the choice is yours.
Example with <script setup>: https://v3.vuejs.org/api/sfc-script-setup.html
<!-- App.vue -->
<template>
<UserDetail #user-detail-submitted="userDetailSubmitted"/>
</template>
<script setup>
import UserDetail from './components/UserDetail';
function userDetailSubmitted(name) {
console.log({ name })
}
</script>
<!-- UserDetail.vue -->
<template>
<input type="text" v-model="name" #keyup.enter="$emit('user-detail-submitted', name)" />
</template>
<script setup>
import { ref } from 'vue';
const name = ref('');
</script>
Example using Composition API: https://v3.vuejs.org/api/composition-api.html
<!-- App.vue -->
<template>
<UserDetail #user-detail-submitted="userDetailSubmitted"/>
</template>
<script>
import UserDetail from "./components/UserDetail";
export default {
components: {
UserDetail,
},
setup() {
function userDetailSubmitted(name) {
console.log({ name });
}
return {
userDetailSubmitted
}
},
};
</script>
<!-- UserDetail.vue -->
<template>
<input type="text" v-model="name" #keyup.enter="$emit('user-detail-submitted', name)" />
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const name = ref('');
return {
name,
}
}
}
</script>
You should import this child-component in the parent. And don't rename it to the html's original tag.vue3. You'd better use the Composition API.

Vuejs unable to access dom element after mounted() even with this.nextTick. Using chartjs

This is my child element
<template lang="html">
<div class="row">
<div class="col-lg-8 col-md-8 col-sm-12 col-xs-12">
<bar-chart :v-if="this.barChartReadyToBeRendered" :chart-data='null' :height="340"></bar-chart>
</div>
<div class="flex-col-docs col-lg-3">
<div class="column" style="height: 150px">
<div class="col">
<q-select dark stack-label="Show Targets" class="select-notification"
v-model="selectTargetNotification"
:options="this.getTargetChangeOptions"
/>
</div>
<div class="col">
<q-select dark stack-label="Agency" class="select-notification"
v-model="selectOrgNotification"
:options="this.getOrganisationOptions"
/>
</div>
</div>
</div>
</div>
</template>
<script>
import BarChart from '../../components/BarChart'
export default {
components: {
BarChart
},
.
.
/* Other code */
mounted () {
console.log('OUTSIDE MOUNTED')
this.$nextTick(() => {
console.log(this.$el)
let ctx = document.getElementById('bar-chart')
console.log('WWWWWWWWWWWWWWWWWW')
console.log(ctx)
console.log(this.$el)
this.createChart('bar-chart')
})
}
</script>
The bar chart chartjs is
<script>
import { Bar, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Bar,
mixins: [reactiveProp],
props: ['options'],
mounted () {
this.renderChart(this.chartData, this.options)
}
}
</script>
<style>
</style>
In my parent element, the template is
<template>
<q-page padding class="row justify-center">
<div style="width: 80vw; max-width: 100vw;">
<div class="flex-row-docs">
<div class="doc-container">
<q-list no-border>
<div class="row justify-start">
<div class="col-6">
<target-changes-agency></target-changes-agency>
</div>
</div>
<div class="q-mb-md q-mt-md q-headline">Full coverage</div>
<span v-if="!isNewsByIdLoaded" class="row justify-center">
<q-spinner-mat :size="36" style="color: #027be3ff; text-align: justify; margin: 2rem;" />
</span>
<div class="row">
<article-cluster :isNewsByIdLoaded="isNewsByIdLoaded"></article-cluster>
</div>
</q-list>
</div>
</div>
</div>
</q-page>
</template>
I am expecting to console.log(ctx) and console.log(this.$el), however the output of those 2 is null and <!-- --> respectively.
I thought mounted and this.$nextTick() will allow me to have access to the DOM. What am i missing here? please help thank you
Why are you assuming that document.getElementById('bar-chart') would return any element? There is no element with that ID being created. What you're rather looking for is document.getElementsByTagName('bar-chart'), but that will also yield no result, because Vue does not internally create Web Components, but inserts the component's root element in place instead. So, what you can do is give your bar-chart component an id attribute, which will be passed to the root element automatically.
The next issue is that your bar-chart component is only visible when the condition in v-if is truthy. That's probably not the case when the component is first being loaded. In this working minimal example, I simply set v-if="false".
const { Bar, mixins } = VueChartJs
const { reactiveProp } = mixins
const BarChart = Vue.component('bar-chart', {
extends: Bar,
mixins: [reactiveProp],
props: ['options'],
mounted () {
//this.renderChart(this.chartData, this.options)
this.$nextTick(() => {
console.log('mounted bar-chart component:');
console.log(this.$el)
});
}
});
Vue.component('example-component', {
template: `<div><bar-chart v-if="false" id="barchart" chart-data="null" height="340"></bar-chart></div>`,
components: [BarChart],
mounted () {
this.$nextTick(() => {
console.log('mounted child component:');
let ctx = document.getElementById('barchart')
console.log(ctx)
console.log(this.$el)
})
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://unpkg.com/vue-chartjs#3.5.0/dist/vue-chartjs.min.js"></script>
<div id="app">
<example-component></example-component>
</div>
(The stack snippet console actually hides the <!-- -->, but you can see it in this codepen. Vue automatically inserts this empty HTML comment as a placeholder for a component that is not currently being displayed.)
The output is actually expected, as the bar-chart component is not being rendered, therefore this.$el (referring to the child component, not the bar-chart component) is empty.
Now here ist the same snippet with v-if="true" on the bar-chart component:
const { Bar, mixins } = VueChartJs
const { reactiveProp } = mixins
const BarChart = Vue.component('bar-chart', {
extends: Bar,
mixins: [reactiveProp],
props: ['options'],
mounted () {
//this.renderChart(this.chartData, this.options)
this.$nextTick(() => {
console.log('mounted bar-chart component:');
console.log(this.$el)
});
}
});
Vue.component('example-component', {
template: `<div><bar-chart v-if="true" id="barchart" chart-data="null" height="340"></bar-chart></div>`,
components: [BarChart],
mounted () {
this.$nextTick(() => {
console.log('mounted child component:');
let ctx = document.getElementById('barchart')
console.log(ctx)
console.log(this.$el)
})
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://unpkg.com/vue-chartjs#3.5.0/dist/vue-chartjs.min.js"></script>
<div id="app">
<example-component></example-component>
</div>
As you can see, the logs now return the correct elements, also in the mounted() hook of the bar-chart component.
Of course, you shouldn't use the id attribute in your component if you ever plan to have multiple instances of this component, because it would result in multiple elements having the same ID, which is invalid HTML and might lead to unexpected interferences. So, this was only for demonstration purposes in this minimal example. In your real code, you could use Vue's ref attribute instead, which you can then refer to via this.$refs inside the parent component.
There are two other issues in your code:
You don't need the colon in front of v-if, because it automatically binds to the expression given as its value.
You don't need this. in your expressions, you're in the components context automatically and can simply use the properties' names directly.