Watching computed properties - vue-component

I have a component with the following hash
{
computed: {
isUserID: {
get: function(){
return this.userId?
}
}
}
Should I be watching isUserID or userId for changes? Can you watch computed properties?

Yes, you can setup watcher on computed property, see the fiddle.
Following is the code to set watch on computed property:
const demo = new Vue({
el: '#demo',
data() {
return {
age: ''
};
},
computed: {
doubleAge() {
return 2 * this.age;
}
},
watch: {
doubleAge(newValue) {
alert(`yes, computed property changed: ${newValue}`);
}
}
});

computed: {
name: {
get: function(){
return this.name;
}
}
},
watch: {
name: function(){
console.log('changed');
}
}
This way we can watch over the computed property if it is changed we get notified on the console.

Here's how you do it in Vue 3 with Composition API:
<script setup>
import { ref, computed, watch } from 'vue'
const variable = ref(1)
const computedProperty = computed(() => {
return variable.value * 2
})
watch(computedProperty, (newValue, oldValue) => {
console.log('computedProperty was ' + oldValue + '. Now it is ' + newValue + '.')
})
</script>
<template>
<button #click="variable++">Click me</button>
</template>

Related

Can I load the component template dynamically

Is it possible to load the template from server (include components) dynamically? Or can I change the template before it rendered?
I would like to let user to store their own form template into a database and generate the form according to the template-id.
I tried to change the this.$options.template, but it seems like only work for vue2.
<!-- static/myproj/js/my-field.vue -->
<template>
<label :for="name+'Fld'" v-html="title"></label>
<input :name="name" :type="text" :value="value" :id="name+'Fld'"/>
</template>
<script>
export default{
props: {
name: {type:String, required:true},
value: {type:String, required:false, default:''},
type: {type:String, required:true},
title: {type:String, required:false, default:'Field: '},
},
data: function(){ return {}; },
}
</script>
// index.vue
const loadVueModuleOpts= {
moduleCache: {vue: Vue},
async getFile(url) {
const res = await fetch(url);
if ( !res.ok )
throw Object.assign(new Error(res.statusText + ' ' + url), { res });
return {
getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
}
},
};
export default{
props: {
id: {required:true, type:String, default:'abcdefg'},
},
data: function(){
this.loadSource();
return {
source: null,
target: null,
};
},
template: '<div>I\'m here to be replaced.</div>',
created: async function(){
this.$options.template=await axios.get(`/api/template/${id}`).then(resp=>resp.data);
},
components: {
'my-field': Vue.defineAsyncComponent( ()=>loadModule('/static/myproj/js/my-field.vue', loadVueModuleOpts)),
}
<!-- server response for /api/template/abcdefg -->
<form action="POST">
<my-field name="name" title="Your Name: " type="text"/>
<my-field name="email" title="Email: " type="email"/>
<input type="submit"/><input type="reset"/>
</form>
Thanks.
Finally, I got the solution. According to Vue3: How to use Vue.compile in Vue3, we can render the template directly by Vue3 like this:
// index.vue
import { h, compile } from 'vue';
const loadVueModuleOpts= {
moduleCache: {vue: Vue},
async getFile(url) {
const res = await fetch(url);
if ( !res.ok )
throw Object.assign(new Error(res.statusText + ' ' + url), { res });
return {
getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
}
},
};
export default{
props: {
id: {required:true, type:String, default:'abcdefg'},
},
data: function(){
this.loadSource();
return {
source: null,
target: null,
};
},
// Magic here
render: function(){
if(this.target)
return h(compile(this.target).bind(this));
return h('div', 'Loading...');
},
created: async function(){
this.$options.template=await axios.get(`/api/template/${id}`).then(resp=>resp.data);
},
components: {
'my-field': Vue.defineAsyncComponent( ()=>loadModule('/static/myproj/js/my-field.vue', loadVueModuleOpts)),
},
}

How to display data from firebase in vis.js timeline

I m using vis.js timeline and i want display date from firestore. It works when I type manually (look --> this.items), but does not work with firestore (look --> this.users).
I m using Vue framework.
<script>
export default {
data() {
return {
users: [],
items: [
{
id: '1',
content: 'London',
group: 'Mike',
start: '2021-12-20',
end: '2022-06-19',
},
],
}
},
async fetch() {
await this.loadPlaces()
},
methods: {
async loadPlaces() {
const querySnapshot = await getDocs(collection(db, 'places'))
querySnapshot.forEach((doc) => {
this.users.push({ id: doc.id, ...doc.data() })
})
this.$store.commit('places/setPlaces', this.users)
},
},
computed: {
places() {
return this.$store.state.places.places
},
},
mounted() {
let container = document.getElementById('visualization')
let options = {
moveable: true,
}
let timeline = new vis.Timeline(container)
timeline.setOptions(options)
timeline.setGroups(this.groups)
timeline.setItems(this.items)
},
}
</script>
I found a solution.
I just moved all code from mounted() to method loadPlaces (under this.$store.commit)
Save yourself trouble and use the vis datasets instead.
my pinia store in vue 3 looks like this.
import { defineStore } from 'pinia'
import { DataSet } from 'vis-data/esnext'
export const useVisData = defineStore('visData', {
state: () => ({
items: new DataSet([]),
groups: new DataSet([]),
selectedItems: [],
serializedGroupsAndItems: []
}),
actions: {
//Group actions
showAllGroups() {
this.groups.forEach(group => {
this.groups.updateOnly({ id: group.id, visible: true })
});
},
addGroup(group) {
this.groups.add(group)
},
hideGroup(group) {
this.groups.updateOnly({ id: group, visible: false })
},
//Item actions
addItem(item) {
this.items.add(item)
},
removeItem(item) {
this.items.remove(item)
},
setSelectedItems(items) {
this.selectedItems = items
},
//data add/remove
serializeData() {
this.serializedGroupsAndItems.push({
groups: JSON.stringify(this.groups.get()),
items: JSON.stringify(this.items.get())
})
},
loadSerializedData() {
this.clearGroupsAndItems()
this.serializedGroupsAndItems.forEach(data => {
this.addGroup(JSON.parse([data.groups]))
this.addItem(JSON.parse([data.items]))
})
},
//misc
clearGroupsAndItems() {
this.groups.clear()
this.items.clear()
}
},
getters: {
getHiddenGroups(state) {
return state.groups.get({
filter: (item) => {
return item.visible === false
}
})
}
}
})
Also remember to watch for changes in your options.
Might be better to wrap it in a vue component too. something like this.
this is what i did.
let timeline;
const visref = ref(null);
onMounted(async () => {
timeline = new Timeline(visref.value, props.items, props.groups, {...props.options, ...timelineOptions});
props.events.forEach(event => {
on(event, (properties) => {
// console.log(event, properties)
emits(`vis${event}`, properties);
});
});
})
<template>
<div ref="visref"></div>
</template>
then you can use it like so:
const timelineref = ref();
<Timeline
ref="timelineref"
:items="visStore.items"
:groups="visStore.groups"
:options="options"
/>
remember to expose the instance in your timeline component then you can call the functions using a ref like this.
timelineref.value.timeline.zoomOut(0.5)

Vue 3 compoition API computed function

Trying to switch my code to the new composition API that comes with Vue 3 but I cant get it to work.
export default {
props: {
classProp: {type: String},
error: {type: String},
},
setup(){
// move to here (this is not working)
computed(() => {
const classObject = () => {
return ['form__control', this.classProp,
{
'form__invalid': this.error
}
]
}
})
},
computed: {
classObject: function () {
return ['form__control', this.classProp,
{
'form__invalid': this.error
}
]
}
},
}
skip "computed" all together
you need to use "ref" or "reactive". these are modules:
<script>
import { ref } from 'vue'
setup(){
const whateverObject = ref({ prop: "whatever initial value" });
whateverObject.value.prop= "if you change something within setup you need to access it trough .value";
return { whateverObject } // expose it to the template by returning it
}
</script>
if you want to use classes you import them like in this example of my own:
import { APIBroker } from '~/helpers/APIbroker'
const api = new APIBroker({})
Now "api" can be used inside setup() or wherever

img not loading from :src

I am getting the image location from firestore and would like to show the image using v-bind:src. You can find my code below:
<b-avatar v-bind:src = "profilepic" class="mr-5" size="8em"></b-avatar>
my methods can be found below:
export default {
data() {
return {
uid: "",
profilepic: "",
}
},
methods: {
getprofilepic() {
fb.storage().ref('users/' + this.uid + '/profile.jpg').getDownloadURL().then(imgURL => {
this.profilepic = imgURL;
alert(this.profilepic); // shows the correct path
})
},
}
created() {
this.uid = fb.auth().currentUser.uid;
this.getprofilepic();
}
}
I am confident that this.profilepic is storing the correct path as if i were to manually type in the path, it will show. I am suspecting that the page loaded before path could be retrieve from firestore. How can i work around this? Thank you in advance.
I have tried hardcoding the path directly to the data and it works fine. The code can be found below:
data() {
return {
uid: "",
profilepic: "*my firebase storage path*",
}
},
With that im not really sure why isnt it still showing
In the script below the template tags, you need to make sure to include the image, and of course, instead of putting my path, put your image's path!
<script>
export default {
data() {
return {
files: {
my_pic: require('/src/assets/img/avatars/logo.png')
}
};
},
}
};
</script>
Then in your and where you want to put your image, you need to put it in this format
<img :src="files.my_pic">
Let me know if this helps or if you want me to expand more.
Try waiting for the uuid to get retrieved:
<template>
<img height="200" v-bind:src = "profilepic" />
</template>
<script>
export default {
data() {
return {
uuid: undefined,
profilepic: undefined
}
},
methods: {
getprofilepic() {
fb.storage().ref('users/' + this.uid + '/profile.jpg').getDownloadURL()
.then(imgURL => {
this.profilepic = imgURL;
})
},
getuuid(){
return new Promise((resolve, reject) => {
var user = fb.auth().currentUser;
if(user == null) reject()
if (user) resolve(user.uid)
})
}
},
created() {
this.getuuid()
.then(uuid => this.uuid = uuid)
.then(() => {
this.getprofilepic();
})
}
};
</script>
As you can see in this example, it does not matter how long it takes for the URL to load: Vue SFC Playground
When Vue Loader compiles the <template> blocks in SFCs, it also converts any encountered asset URLs into webpack module requests.
For example, the following template snippet:
<img src="../image.png">
will be compiled into:
createElement('img', {
attrs: {
src: require('../image.png') // this is now a module request
}
})
By default the following tag/attribute combinations are transformed, and can be configured using the transformAssetUrls option.
{
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: ['xlink:href', 'href'],
use: ['xlink:href', 'href']
}
Step 1: Create vue.config.js
module.exports = {
productionSourceMap: false,
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options['transformAssetUrls'] = {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href',
'b-avatar': 'src',
'b-img': 'src',
'b-img-lazy': ['src', 'blank-src'],
'b-card': 'img-src',
'b-card-img': 'src',
'b-card-img-lazy': ['src', 'blank-src'],
'b-carousel-slide': 'img-src',
'b-embed': 'src'
}
return options
})
}
}
Step 2: Inside main.js import vue.config
import '../vue.config'
Step 3: Create your html template
<template>
<b-avatar :src="profilepic" class="mr-5" size="8em"></b-avatar>
</template>
<script>
import { BAvatar } from 'bootstrap-vue'
export default {
name: 'bootstrap-image-avatar',
components: {
'b-avatar': BAvatar
},
data() {
return {
uid: "",
profilepic: "",
}
},
methods: {
getprofilepic() {
fb.storage().ref('users/' + this.uid + '/profile.jpg').getDownloadURL().then(imgURL => {
this.profilepic = imgURL;
alert(this.profilepic); // shows the correct path
})
},
}
created() {
this.uid = fb.auth().currentUser.uid;
this.getprofilepic();
}
}
</script>

Conditional navbar based on a user authentication status

I'm trying to conditionally display navbar elements of a navigation component based on the onAuthStateChanged Firebase function.
<template>
<navbar dark position="top" class="default-color" scrolling>
<mdb-navbar-brand href="#/" style="font-weight: bolder;">
Test
</mdb-navbar-brand>
<navbar-collapse>
<navbar-nav left>
<navbar-item href="#/" waves-fixed>Home</navbar-item>
<navbar-item href="#/css" waves-fixed>About</navbar-item>
<navbar-item href="#/jobs" waves-fixed>Jobs</navbar-item>
<navbar-item href="#/advanced" waves-fixed>Profile</navbar-item>
</navbar-nav>
<navbar-nav right>
<router-link to="/signup"><button v-if="!user" type="button" class="btn btn-primary">Signup</button></router-link>
<router-link to="/login"><button v-if="!user" type="button" class="btn btn-primary">Login</button></router-link>
<p><a v-if="user" #click="logout">Logout</a></p>
</navbar-nav>
</navbar-collapse>
</navbar>
</template>
<script>
import Navbar from '#/components/Navbar.vue';
import NavbarItem from '#/components/NavbarItem.vue';
import NavbarNav from '#/components/NavbarNav.vue';
import NavbarCollapse from '#/components/NavbarCollapse.vue';
import mdbNavbarBrand from '#/components/NavbarBrand.vue';
import firebase from 'firebase';
export default {
name: 'Navigation',
data() {
return {
user: null,
};
},
components: {
Navbar,
NavbarItem,
NavbarNav,
NavbarCollapse,
mdbNavbarBrand
},
methods: {
logout() {
firebase.auth().signOut()
.then(() => {
this.$router.push({path: '/'});
});
},
created() {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
this.user = user;
} else {
this.user = null;
}
});
}
}
};
</script>
Unfortunately, for some reason, the onAuthStateChanged is not working. I also tried to simply display the user in the console from the component perspective, but it's not working as well:
console.log(firebase.auth().currentUser);
Thanks in advance for any hints.
I just wanted to point out another option. Renaud Tarnec's answer is correct but there is a second solution.
You can use the arrow function syntax. With arrow functions the context doesnt change so there is no need to set vm = this before the function since this will still work inside the function. I'm a huge fan of lambda/arrow functions and see no reason not to use them.
Renaud Tarnec's should be the accepted answer but just wanted to offer a second option :)
export default {
name: 'Navigation',
data() {
return {
user: null,
};
},
components: {
Navbar,
NavbarItem,
NavbarNav,
NavbarCollapse,
mdbNavbarBrand
},
methods: {
....
}
},
created: function () {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.user = user;
} else {
this.user = null;
}
});
}
};
If you want to call firebase.auth().onAuthStateChanged() in the created lifecycle hook you should do as follows:
export default {
name: 'Navigation',
data() {
return {
user: null,
};
},
components: {
Navbar,
NavbarItem,
NavbarNav,
NavbarCollapse,
mdbNavbarBrand
},
methods: {
....
}
},
created: function () {
var vm = this;
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
vm.user = user;
} else {
vm.user = null;
}
});
}
};
The way you do it, you are declaring created as a "standard" component method.

Resources