Vue components exposes this.$data. Is there any way to access computed properties in a similar fashion?
They are not exposed on $data, and there is no such thing as this.$computed
There's no built-in way to access an object with the computed properties of a Vue instance.
If you really want an object of computed property name and values for testing purposes you could define your own $computed property using the information in the _computedWatchers property. This might be finicky and I wouldn't use it in production code.
Object.defineProperty(Vue.prototype, '$computed', {
get() {
let computed = {};
Object.keys(this._computedWatchers).forEach((key) => {
computed[key] = this._computedWatchers[key].value;
})
return computed;
}
})
new Vue({
el: '#app',
data() {
return {
foo: 1,
}
},
computed: {
bar() {
return this.foo * 2;
}
},
mounted() {
console.log(this.$computed)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">{{ bar }}</div>
Related
I need global variables for errors. But I don't want set input variable for every component.
How I can watch $errors in component ABC without input variable?
(without <abc :errors="$errors"></abc>)
index.js:
Vue.prototype.$errors = {};
new Vue({
el: '#app',
render: h => h(App),
}
App.vue:
...
name: 'App',
components: {
ABC
}
...
methods:{
getContent() {
this.$errors = ...from axis...
}
Component ABC:
<template>
<div>{{ error }}</div>
</template>
...
watch: {
???
}
Here's an example of how it could be done:
const errors = Vue.observable({ errors: {} })
Object.defineProperty(Vue.prototype, '$errors', {
get () {
return errors.errors
},
set (value) {
errors.errors = value
}
})
new Vue({
el: '#app',
methods: {
newErrors () {
// Generate some random errors
const errors = {}
for (const property of ['name', 'type', 'id']) {
if (Math.random() < 0.5) {
errors[property] = 'Invalid value'
}
}
this.$errors = errors
}
}
})
new Vue({
el: '#app2',
watch: {
$errors () {
console.log('$errors has changed')
}
}
});
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<pre>{{ $errors }}</pre>
<button #click="newErrors">New errors</button>
</div>
<div id="app2">
<pre>{{ $errors }}</pre>
</div>
I've created two Vue instances to illustrate that the value really is shared. Clicking the button in the first instance will update the value of $errors and the watch is triggered in the second instance.
There are a few tricks in play here.
Firstly, reactivity can only track the reading and writing of properties of an observable object. So the first thing we do is create a suitable object:
const errors = Vue.observable({ errors: {} })
We then need to wire this up to Vue.prototype.$errors. By defining a get and set for that property we can proxy through to the underlying property within our observable object.
All of this is pretty close to how data properties work behind the scenes. For the data properties the observable object is called $data. Vue then uses defineProperty with get and set to proxy though from the Vue instance to the $data object, just like in my example.
as Estradiaz said:
You can use Vuex and access the value outside of Vue like in this answer: https://stackoverflow.com/a/47575742/10219239
This is an addition to Skirtles answer:
You can access such variables via Vue.prototype.variable.
You can set them directly, or use Vue.set, it works either way.
My code (basically the same as Skirtless):
main.js
const mobile = Vue.observable({ mobile: {} });
Object.defineProperty(Vue.prototype, '$mobile', {
get() { return mobile.mobile; },
set(value) { mobile.mobile = value; }
});
function widthChanged() {
if (window.innerWidth <= 768) {
if (!Vue.prototype.$mobile) Vue.set(Vue.prototype, '$mobile', true);
} else if (Vue.prototype.$mobile) Vue.set(Vue.prototype, '$mobile', false);
}
window.addEventListener("resize", widthChanged);
widthChanged();
Home.vue:
watch: {
'$mobile'(newValue) {
// react to Change in width
}
}
Say I start out like this:
data() {
return {
user: {}
}
}
And then later on... I make an API call:
let response = await this.axios.get("/api/users/1.json");
this.user = response.data
Is this the proper way to assign the data from my API call to this.user?
Do all of the properties of user need to be defined first?
No, you don't have to declare all properties of user. Just having user in the data with any value different from undefined will suffice.
When the property is in the data, Vue will replace it with a getter and a setter. When you do:
this.user = newValue;
You are actually calling a setter that will map all properties of the newValue into getters themselves.
In the official docs, you can find more info on the Reactivity in Depth page.
Have a look below. Check the demo and the explanation in the image that shows the object that was created (and printed in the console).
var app = new Vue({
el: '#app',
data: {
other: null,
user: null
},
mounted() {
this.user = {
name: 'bob',
age: 10
}
}
})
console.dir(app)
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ other }}</p>
<p>{{ user }}</p>
</div>
No you dont need to put properties in it, you can do it like this. You create a method and make a axios inside it:
data() {
return {
user: {}
}
},
methods: {
axiosCall: function (){
axios.get("/api/users/1.json").then(response =>{
this.user = response.data
})
}
}
I am trying to create custom event handlers for child components/elements of the functional component. The problem is that when using a render() function to create the child components, I cannot access their this context.
Suppose we have the following functional component:
const Aggregate = {
functional: true,
props: {
value: Object // to work with v-model
},
render: function(createElement, context){
const template = []
const inputHandler = function(value, prop){
const data = Object.assign({}, context.props.value, { [prop]: value })
console.log(context.props.value)
console.log(data)
this.$emit('input', data)
}
for (const prop of Object.keys(context.props.value)){
const child = createElement('input', {
props: {
value: context.props[prop]
},
on: {
input: function(event){
// 'this' is not binded here - it is undefined,
// hence the inputHandler() function is
// rising an error
inputHandler.apply(this, [event.target.value, prop])
}
}
})
template.push(child)
}
return template
}
}
Is it possible to access this context for a vnode, when creating event handler this way?
P.S. Use case info: I want to implement a component that automatically generates <input> elements for a resource and uses two-way binding through v-model directive. I also want to use it in <table> where wrapping in <td> will be required, thus I made the component functional.
Functional components don't a have a "this", because there is no Vue instance for them. This makes them lightweight.
This also means emiting events from them is kind of harder, since you need to implement Vue's logic yourself.
Lacking an instance doesn't mean you cannot events, instead, you need to manually parse context.listeners and call the event handler manually. In the case of v-model, you need to call the input listener:
const Aggregate = {
functional: true,
props: {
value: Object // to work with v-model
},
render: function(createElement, context){
const template = []
const inputHandler = function(value, prop, handler){
const data = Object.assign({}, context.props.value, { [prop]: value })
console.log(context.props.value)
console.log(data)
// Call handler directly instead of using this.$emit
handler(data)
}
for (const prop of Object.keys(context.props.value)){
console.log(context.props.value, prop)
const child = createElement('input', {
// Small bug fixes in the following section:
domProps: {
value: context.props.value[prop]
},
// End bug fixes
on: {
input: function(event){
// pass `context.listeners.input` instead of binding here
inputHandler(event.target.value, prop, context.listeners.input)
}
}
})
template.push(child)
}
return template
}
}
new Vue({
el: "#app",
components: {
Aggregate
},
data: {
test: {
key1: "val1",
key2: "val2",
}
},
})
<!-- development version, includes helpful console warnings -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<aggregate v-model="test"></aggregate>
<pre>{{ test }}</pre>
<button #click="test = {...test, ping: 'pong'}">Add key</button>
</div>
I don't know why I cant access reactive value in methods.
...
<div class="card">
<div class="card-contents">
<datafieldcheckbox class="filterComponents" :filtervalue="filterAll" #call-method="callfilteredproducts"></datafieldcheckbox>
</div>
</div>
....
new Vue({
el: "#app",
data() {
return {
filterAll: this.filtered(),
dataCategory : ["data"]
}
},
.....
methods: {
filtered() {
console.log("this.data", this.dataCategory) // Got undefined insted of getting value.
}
...
When filtered method is called, data isn't fully setup yet. It makes sense that dataCategory is not available. Instead, call it in created hook, where data is already available.
export default {
data() {
return {
filterAll: null,
dataCategory: ["data"]
};
},
methods: {
filtered() {
console.log("this.data", this.dataCategory); // Got undefined insted of getting value.
}
},
created() {
this.filterAll = this.filtered();
}
};
( P.S. Not sure what you are trying to achieve. But it does seem wrong. )
How can I use a computed property in the data or emit it via bus?
I have the following vue instance, but myComputed is always undefined but computedData is working correctly.
var vm = new Vue({
data(){
return{
myComputed: this.computedData
}
},
computed: {
computedData(){
return 'Hello World'
}
}
})
Unfortunately, it is impossible to use computed property in data because of component creation timing: data evaluates Before computed properties.
To make things as simple as possible, just do the work in watcher, unless you want to emit the changes to different components or there're a lot of variables you want to notify, then you may have to use Vuex or the event bus:
var vm = new Vue({
data(){
return{
myComputed: '',
computedData: 'Hello World'
}
},
created() {
this.myComputed = this.computedData;
},
watch: {
computedData() {
this.myComputed = this.computedData;
}
}
});
Computed is already accessible in the template using {{ }}.
But you can use the
watch:{
//your function here
}
instead of computed
If you are using computed/reactive objects then it should be inside the computed and not inside the data.
Simply change your code to use computed instead of data
var vm = new Vue({
data(){
return{}
},
computed: {
computedData(){
return 'Hello World'
},
myComputed(){
return this.computedData
}
}
})
you are trying to use data as computed and this shall not be.
data doesn't act like computed object.
and it's not because of component creation timing. What if we changed the component creation timing ? this will not solve anything as data will take only the first computed value(only one) and will not update after.
you can work just with the computed function
var vm = new Vue({
data(){
return{
//is not necessary
}
},
computed: {
computedData(){
return 'Hello World'
}
}
})
and in your template
<template>
<div>{{ computedData }}</div>
</template>
You are over-coding it. Computed props are accessible in the same manner as data props in your template.
var vm = new Vue({
computed: {
myComputed(){
return 'Hello World'
}
}
})
In the template you have access to this just like you do to data:
<template>
<div>{{ myComputed }}</div>
</template>
https://v2.vuejs.org/v2/guide/computed.html
Try to convert the computed in a method
var vm = new Vue({
data(){
return{
myComputed: this.computedData
}
},
methods: {
computedData(){
return 'Hello World'
}
}
})
This is simple and it works (NOT reactive), but has a cost:
https://medium.com/notonlycss/the-difference-between-computed-and-methods-in-vue-js-9cb05c59ed98
computed is not available at the time data gets initialized.
If it should be a one-time thing (and NOT reactive), you could achieve this by setting the data at the moment where the computed property is available by using the created() hook:
export default {
data: () => ({
myDataBackend: '',
}),
computed: {
computedData () {
return 'Hello World'
}
},
created() {
this.$set(this, 'myDataBackend', this.computedData)
}
}
Futher reading: Vue Documentation on Lifecycle Hooks
In case you are trying to work with v-model:
You could also use :value and some event like #change or #keyup in the element instead.
:value is the value which the input-element initially works with
After writing some letter in the input field, the #keyup event changes the data.
Typically, events carry the updated form value in target.value
The changeMyData method sets the value
the computed property listens to the data change and the :value of the input field gets updated.
Note: I used data as a data store. But you could also use for example vuex instead.
<template>
<div>
<input
type="text"
:value="computedData"
#keyup="changeMyData"
/>
<p>{{myDataBackend}}</p>
</div>
</template>
<script>
export default {
data: () => ({
myDataBackend: 'Hello World'
}),
methods: {
changeMyData(evt) {
this.$set(this, 'myDataBackend', evt.target.value)
console.log('Changed the value to: ' + evt.target.value)
}
},
computed: {
computedData () {
return this.myDataBackend
}
}
}
</script>