How to track changes to the injected root instance property - vue.js

I have a property injected like so:
Vue.prototype.$authentication = {
authenticated: false,
user: ""
};
and its working fine.
then in another component I want to track/watch the property changes. How do I do it ?
EDIT
I want to be able to do it like this:
<script>
export default {
name: "Login",
data: function(){
return {
isLoggedIn: this.$authentication.authenticated
}
},
watch: {
isLoggedIn(){
console.log('its working');
}
},
}
</script>
but the thing is, the code wouldn't work.

Have it solved by looking at the api doc. So in my component file, I simply do it like so:
<script>
export default {
name: "Login",
data: function(){
return {
isLoggedIn: this.$authentication // remove the attribute
}
},
watch: {
'isLoggedIn.authenticated': function (){ // now access the attribute
console.log('its working');
}
},
}
</script>
now it is all good.

Related

Is it possible to watch injected property

I am building an application which is using Vue 3 and I am providing a property in a parent component which I am subsequently injecting into multiple child components. Is there any way for a component which gets injected with this property to watch it for changes?
The parent component looks something like:
<template>
<child-component/>
<other-child-component #client-update="update_client" />
</template>
<script>
export default {
name: 'App',
data() {
return {
client: {}
}
},
methods: {
update_client(client) {
this.client = client
}
},
provide() {
return {
client: this.client
}
},
}
</script>
The child component looks like:
<script>
export default {
name: 'ChildComponent',
inject: ['client'],
watch: {
client(new_client, old_client) {
console.log('new client: ', new_client);
}
}
}
</script>
I am trying to accomplish that when the provided variable gets updated in the parent the children components where its being injected should get notified. For some reason the client watch method is not getting called when client gets updated.
Is there a better way of accomplishing this?
Update
After further testing I see that there is a bigger issue here, in the child component even after the client has been updated in the parent, the client property remains the original empty object and does not get updated. Since the provided property is reactive all places it is injected should automatically be updated.
Update
When using the Object API reactive definition (data(){return{client:{}}), even though the variable is reactive within the component, the injected value will be static. This is because provide will set it to the value that it is initially set to. To have the reactivity work, you will need to wrap it in a computed
provide(){
return {client: computed(()=>this.client)}
}
docs:
https://vuejs.org/guide/components/provide-inject.html#working-with-reactivity
You may also need to use deep for your watch
Example:
<script>
export default {
name: 'ChildComponent',
inject: ['client'],
watch: {
client: {
handler: (new_client, old_client) => {
console.log('new client: ', new_client);
},
deep: true
}
}
}
</script>
As described in official documentation ( https://v2.vuejs.org/v2/api/#provide-inject ), by default, provide and inject bindings are not reactive. But if you pass down an observed object, properties on that object remain reactive.
For objects, Vue cannot detect property addition or deletion. So the problem in your code might be here:
data() {
return {
client: {}
}
},
Since you change the client property of this object ( this.client.client = client ), you should declare this key in data, like this:
data() {
return {
client: { client: null }
}
},
Now it becomes reactive.
I did a code sandbox reproducing your code watching an injected property: https://codesandbox.io/s/vue-inject-watch-ffh2b
For some reason the only way I got this to work was by only updating properties of the initial injected object instead of replacing the whole object. I also was not able to get watch working with the injected property despite setting deep: true.
Updated parent component:
<template>
<child-component/>
<other-child-component #client-update="update_client" />
</template>
<script>
export default {
name: 'App',
data() {
return {
client: {}
}
},
methods: {
update_client(client) {
this.client.client = client
}
},
provide() {
return {
client: this.client
}
},
}
</script>
Updated child component:
<template>
<button #click="get_client">Get client</button>
</template>
<script>
export default {
name: 'ChildComponent',
inject: ['client'],
methods: {
get_client() {
console.log('updated client: ', client);
}
}
}
</script>
create a new value and reference the value from inject into it
inject: ['client'],
data: () => ({
value: null,
}),
created() {
this.value = this.client;
},
watch: {
value: {
handler() {
/* ... */
},
deep: true,
}
}
Now you can watch the value.
Note: "inject" must be an object
I ran into the same issue. But i just had to look more closely for details in the docs to make it work. In the end everything worked fine for me.
I built a vue plugin providing a Map together with some function as a readonly ref. Then it starts changing the Map contents once a second:
plugin.js
import { ref, readonly } from 'vue';
const rRuns = ref( new Map() );
let time = 0;
export default
{
install(app, defFile)
{
...
app.provide( "runs", readonly(
{ ref: rRuns,
get: (e) => rRuns.value.get( e ),
locationNames: () => rRuns.value.keys(),
size: () => rRuns.value.size,
} ) );
...
setInterval( () =>
{ time++;
const key = (time * 7) % 10;
console.log(" runs update", key, time);
rRuns.value.set( key.toString(), time )
}, 1000);
console.log(" time Interval start" );
}
}
main.js:
import { createApp } from 'vue'
import App from './App.vue'
import plugin from 'plugin.js';
const app = createApp(App);
app.config.unwrapInjectedRef = true;
app.use(game, 'gamedefs.json');
app.mount('#app');
runs.vue:
<template>
<h1>Runs:</h1>
<p v-if="!runs.size()">< no runs ></p>
<p v-else>runs: {{ runs.size() }}</p>
<button v-for="r of runs.locationNames()" :key="r" #click="display( r )">[{{ r }}]</button>
</template>
<script>
export default {
name: 'Runs',
inject:
{
runs: { from: 'runs' },
},
watch:
{
'runs.ref':
{
handler( v )
{
console.log("runs.ref watch", v );
},
immediate: true,
deep: true,
},
},
}
</script>

nested object reactivity vue js

I have the following setup for my vue application
var store = {
...
state: {
currentCustomer:{},
},
};
current customer has a property that is an object called payment method
app:
var app= new Vue({
el:'#application',
data: {
sharedState: store.state
}
});
and a couple of components:
Vue.component('user_search', {
template: '#user_search-template',
data() {
return {
sharedState: store.state
}
},
methods: {
getCustomerData: function () {
this.sharedState.currentCustomer(c);
}
mounted: function () {
...
}
});
and
Vue.component('paymentdetails',{
template: '#payment_details_template',
data(){
return{
sharedState: store.state
}
},
mounted:function(){
...
}});
The issue is like this. The payment method component does not bind to the payment details object that is nested inside the current customer object
any suggestions?
Yeah, I think what you are looking for is a computed property for accessing the data.
Vue.component('paymentdetails',{
template: '#payment_details_template',
computed{
sharedState() {
return store.state
}
},
mounted:function(){
...
}});
Maybe give that a try and see how it works.

Global EventBus to pass data between components does not work

I am trying to use a global eventbus in VueJs but have been unsuccessful so far.
I have the following code. When I navigate from ResetPassword to Login screen, I should see the successMessage with a Your password has been changed successfully. Please login to continue but it always shows a blank.
What could I be doing wrong?
plugins.js:
Vue.prototype.$eventHub = new Vue();
ChangePassword.vue:
methods:
{
ChangePassword()
{
this.$eventHub.$emit('navigation-message', 'Your password has been changed successfully. Please login to continue.');
this.$router.push({ name: 'login'});
},
},
Login.vue:
data() {
return {
successMessage:'',
};
},
created ()
{
this.$eventHub.$once('navigation-message', this.successMessage);
},
beforeDestroy()
{
this.$eventHub.$off('navigation-message');
},
Update: 12/8/2019: I changed the login.vue as per comment by #tony19 but the issue still exists.
Login.vue:
created ()
{
this.$eventHub.$once('navigation-message', (payload)=>
{
updateSuccessMessage(payload);
});
},
methods:
{
updateSuccessMessage(payload)
{
this.successMessage=payload;
},
You need to add this.
created () {
this.$eventHub.$on('navigation-message', payload => {
this.updateSuccessMessage(payload)
})
},
methods: {
updateSuccessMessage(payload) {
this.successMessage = payload
}
}
Also make sure you're actually importing plugin.js globally (e.g. inside your main file where you import Vue) and make sure your components have access to it.
Try this:
created() {
console.log(this.$eventHub)
}

How to use the Vue component $set to set object in single file component

Following Vue.js tutorial, I read this part here https://v2.vuejs.org/v2/guide/list.html#Object-Change-Detection-Caveats:
var vm = new Vue({
data: {
userProfile: {
name: 'Anika'
}
}
})
// It was stated in the tutorial that, the below will add to userProfile
// while making sure userProfile still being reactive
vm.$set(vm.userProfile, 'age', 27)
Nice, would like to try that vm.$set function.
However, my implementation is in a single file component, e.g. something like this:
<script>
export default {
name: 'Test',
data() {
return {
userProfile: {
name: 'Anika'
}
};
}
}
</script>
How would I be able to retrive the vm variable above? I have tried the following code but didn't work... With error:
vm.$set is not a function
<script>
let vm = {
name: 'Test',
data() {
return {
userProfile: {
name: 'Anika'
}
};
}
}
vm.$set(vm.userProfile, 'age', 27)
export default vm;
</script>
if you use it inside the context of your component, you just
this.$set(this.userProfile,....)

Vuex Store Automatically Updated

My Vuex store gets automatically updated without calling any getters or committing any mutation on immediate router change.
I am not committing changes to VUEX until the form is saved, so it means the data is bound two way to VUEX. I was under the impression this was not possible. In this case it is not desired since if the user changes some data, then navigates away without actually clicking "save", the data is VUEX is still changed
<template>
<h1>{{userName}}</h1>
<input type="text" v-model="name.selected" :placeholder="user.username" #keyup.enter="Confirm"/>
</template>
<script>
import { mapGetters } from 'vuex'
import { updateUserData } from 'mesh-shared/api/'
export default {
name: 'PreferenceProfile',
data() {
return {
name: {
isActive: false,
selected: '',
onChange: false
}
}
},
computed: {
...mapGetters([
'user',
'preference'
]),
userName() {
if (this.name.selected !== '') {
return this.name.selected
} else {
return this.user.username
}
}
},
methods: {
toggleNameRider() {
this.name.isActive = true
},
async Confirm() {
const params = {
userId: this.user.userId,
accessToken: this.user.auth.accessToken,
avatar: (this.avatar.uploadData.url) ? this.avatar.uploadData.url : this.user.avatar,
username: this.userName
}
const data = await updateUserData(params)
console.log(data)
const user = Object.assign(this.user, {
completed: true
}, data.data.user)
this.cancel()
},
cancel() {
this.avatar.newUrl = ''
this.name.selected = ''
this.name.isActive = false
}
}
}
</script>
I recommend you to do something like this. as I understand from your question you're setting v-model to getter.
instead of doing that check the example below. I hope it can help.