VueJS Syntax: Saving a value in a Promise - javascript

With some help from StackOverflow I got the following to run my loadData when the page loads and when the button is clicked.
However the text on the page is not updating. Something is wrong about my syntax with this.text = xhr.data
index.html:
<div id="app"></div>
app.js:
const Vue = window.Vue = require("vue");
Vue.prototype.$http = require("axios");
const App = require("./components/App.vue");
window.app = new Vue({
el: "#app",
render: h => h(App)
});
components/app.vue:
<template>
<div>
<h1>Test</h1>
<p>{{text}}</p>
<button #click="this.loadData">Reload</button>
</div>
</template>
<script>
export default {
mounted() {
this.loadData();
},
methods: {
loadData() {
this.$http.get("https://icanhazip.com")
// This fails
.then(xhr => this.text = xhr.data);
}
}
};
</script>

You must to define your text property in components data.
From Vue.js documentation:
Due to the limitations of modern JavaScript (and the abandonment of Object.observe), Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive. For example:
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` is now reactive
vm.b = 2
// `vm.b` is NOT reactive
In your case your component should look like this:
<script>
export default {
created() {
this.loadData();
},
data() {
return {
text: '',
};
},
methods: {
loadData() {
this.$http.get("https://icanhazip.com")
// This fails
.then(xhr => this.text = xhr.data);
}
}
};
</script>

Related

Vue.js: How to Use a Variable from Another Component in a Method

Let's say I have some data in a Vue component (we'll call it Data.vue) like this:
<script>
export default {
data(){
return{
data1: {some data}
}
}
}
</script>
I want to use that data on another page (Main.vue) in a method.
<script>
export default {
methods: {
someMethod: function(){
console.log(this.data1)
}
}
}
</script>
How would I go about doing this?
I did try importing it the data to "Main.vue" like this:
import { data1 } from '#/components/Data'
What other steps do I need to take?
Your Q is too general because It depends (From child to parent? global data? read-only data? and so on).
One basic way is by props. Example: Pass data (array/object/string and so on) From parent-to-child
https://v2.vuejs.org/v2/guide/components-props.html
"Hello world example"
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('child', {
// camelCase in JavaScript
props: ['message_from_parent'],
template: '<h3>{{ message_from_parent }}</h3>',
created: function () {
console.log(this.message_from_parent)
}
})
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<child v-bind:message_from_parent="message"></child>
</div>
Or the opposite (From child to parent) by $emit:
https://v2.vuejs.org/v2/api/#vm-on
You may have more than one component Data and then it doesn't becomes clear which one you want to share.
If you have only one component then you can just export data
<script>
const data1 = {some data}
export { data1 }
export default {
data () {
return {
data1
}
}
}
</script>
and then your import should work.
import { data1 } from '#/components/Data'
But this is kindof hacky, you should use a store live vuex

Vuex - Sharing common functions across modules

I am working on a Vue Js 2 application and I'm currently building the store and the different modules to separate out the code.
Is there a way to write a common function and share it across all modules?
For example, I have a function truncate() that I need to be use in customer.js, cart.js, address.js.
If I declare it in store.js and try to use in modules, it throws an error.
Is export and import the only way?
What is the best way to share the function?
The simplest case is, naturally, to just define a regular function in a js file and import/use it anywhere you need it.
There are Vue-specific approaches, though:
For common reusable functions in Vuex modules, you can use Vuex Plugins.
Check an example below. Mind the usage at the root store: plugins: [myTruncatePlugin].
const myTruncatePlugin = store => {
store.truncate = function(str) {
return str.replace(/-/g, '') + ' ...was truncaaaated!'; // example implementation
}
}
const moduleA = {
namespaced: true,
state: {name: "name#moduleA"},
mutations: { changeName(state, data) { state.name = this.truncate(data); } },
}
const moduleB = {
namespaced: true,
state: {title: "title#moduleB"},
mutations: { changeTitle(state, data) { state.title = this.truncate(data); } },
}
const myStore = new Vuex.Store({
strict: true,
modules: {
aaa: moduleA,
bbb: moduleB
},
plugins: [myTruncatePlugin] // IMPORTANT: YOU MUST DECLARE IT HERE
});
new Vue({
store: myStore,
el: '#app',
mounted: function() {
setTimeout(() => {
this.changeName("-n-e-w-N-A-M-E-");
this.changeTitle("-n-e-w-T-I-T-L-E-");
}, 200);
},
computed: {
...Vuex.mapState('aaa', ['name']),
...Vuex.mapState('bbb', ['title'])
},
methods: {
...Vuex.mapMutations('aaa', ['changeName']),
...Vuex.mapMutations('bbb', ['changeTitle'])
}
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
<p>moduleA's name: {{ name }}</p>
<p>moduleB's title: {{ title }}</p>
</div>
For common reusable functions in Vue instances, you can use Mixins. For the most general case there's the Global Mixin (use with care):
Vue.mixin({
methods: {
truncate(str) {
return str.replace(/-/g, '') + ' ...was truncaaaated!'; // example implementation
}
}
})
// this.truncate() will be available in all Vue instances...
new Vue({
el: '#app1',
data: {myStr1: '-o-n-e-'},
mounted() { this.myStr1 = this.truncate(this.myStr1); }
})
new Vue({
el: '#app2',
data: {myStr2: '-t-w-o-'},
mounted() { this.myStr2 = this.truncate(this.myStr2); }
})
// ...and components
Vue.component('my-comp', {
template: '#t3',
data() { return {myStr3: '-t-h-r-e-e-'} },
mounted() { this.myStr3 = this.truncate(this.myStr3); }
});
new Vue({
el: '#app3',
})
<script src="https://unpkg.com/vue#2.5.16/dist/vue.min.js"></script>
<div id="app1">App1: "{{ myStr1 }}"</div>
<div id="app2">App2: "{{ myStr2 }}"</div>
<template id="t3">
<div>App3's component: "{{ myStr3 }}"</div>
</template>
<div id="app3"><my-comp></my-comp></div>
#acdcjunior has the best answer using Mixins, but I'm giving you another option by just declaring method in your Vue instance.
So belows example, I am creating doTruncate method in Vue instance then the components are calling them by this.$parent.doTruncate
// register
Vue.component('cart-component', {
template: '<button #click="doTruncate">Cart Truncate!</button>',
methods: {
doTruncate: function() {
this.$parent.doTruncate("Hello from cart");
}
}
})
// register
Vue.component('customer-component', {
template: '<button #click="doTruncate">Customer Truncate!</button>',
methods: {
doTruncate: function() {
this.$parent.doTruncate("Hello from customer");
}
}
})
var app3 = new Vue({
el: '#app',
methods: {
doTruncate: function(params) {
alert(params);
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.15/dist/vue.js"></script>
<div id="app">
<cart-component></cart-component>
<br>
<customer-component></customer-component>
<br>
<button #click="doTruncate('Hello from parent')">
Parent!
</button>
</div>
you can use vue js events to share function like
eventBus.js // it will create common instance
import Vue from 'vue';
export const eventBus = new Vue();
common.js // your common functions will go into this file
import { eventBus } from '<path of file>';
mounted() {
eventBus.$on('truncate',()=> {
this.truncate();
})
}
methods: {
truncate(){
//truncate code
}
}
customer.js // call your common truncate function from customer.js
import { eventBus } from '<path of file>';
eventBus.$emit('truncate');

Mocking Vue class component methods with inject loader

Let's say I have a very basic vue-class-component as shown below:
<template>
<div>Nothing of interest here</div>
</template>
<script>
import Vue from 'vue';
import Component from 'vue-class-component';
import http from './../modules/http';
#Component
export default class Example extends Vue {
user = null;
errors = null;
/**
* #returns {string}
*/
getUserId() {
return this.$route.params.userId;
}
fetchUser() {
this.user = null;
this.errors = null;
http.get('/v1/auth/user/' + this.getUserId())
.then(response => this.user = response.data)
.catch(e => this.errors = e);
}
}
</script>
I want to test the fetchUser() method so I just mock the './../modules/http' dependency and make http.get return a Promise. The problem is that in my assertion I want to check if the URL is being built properly and in order to do so the user ID has to come from an hard-coded variable in the test.
I tried something like this but it doesn't work:
import ExampleInjector from '!!vue-loader?inject!./../../../src/components/Example.vue';
const mockedComponent = ExampleInjector({
'./../modules/http': {
get: () => new Promise(/* some logic */)
},
methods: getUserId: () => 'my_mocked_user_id'
});
Unfortunately it doesn't work and I could not find anything this specific in the Vue docs so the question is, how am I supposed to mock both external dependencies and a class component method?
NOTE: I do not want to mock this.$route.params.userId as the userId could potentially come from somewhere else as well (plus this is just an example). I just want to mock the getUserId method.
Since I specifically asked about how I could mock Vue class component methods with the inject loader here's the complete solution for the question at hand:
import ExampleInjector from '!!vue-loader?inject!./../../../src/components/Example.vue';
const getComponentWithMockedUserId = (mockedUserId, mockedComponent = null, methods = {}) => {
if (!mockedComponent) {
mockedComponent = ExampleInjector();
}
return Vue.component('test', {
extends: mockedComponent,
methods: {
getUserId: () => mockedUserId,
...methods
}
});
};
And then in my test case:
const component = getComponentWithMockedUserId('my_mocked_user_id');
const vm = new Vue(component);
I found this to be very helpful when you need to create a partial mock AND inject some dependencies too.
The easiest way to do this is to extend the component and override the method:
const Foo = Vue.extend({
template: `<div>{{iAm}}</div>`,
created() {
this.whoAmI();
},
methods: {
whoAmI() {
this.iAm = 'I am foo';
}
},
data() {
return {
iAm: ''
}
}
})
Vue.component('bar', {
extends: Foo,
methods: {
whoAmI() {
this.iAm = 'I am bar';
}
}
})
new Vue({
el: '#app'
})
In this example I'm using the extends property to tell Vue that Bar extends Foo and then I'm overriding the whoAmI method, you can see this is action here: https://jsfiddle.net/pxr34tuz/
I use something similar in one of my open source projects, which you can check out here. All I'm doing in that example is switching off the required property for the props to stop Vue throwing console errors at me.

Vue.js : Passing an external variable to a component through initialization?

I am trying to pass a variable (here, externalVar) to a component, directly when initializing. But I can't find how to do it (probably not understanding documentation well :/ ). What is the correct way to do it?
The initialization :
var externalVar = "hello world"
const leftmenu = new Vue({
el: "#left-menu",
template: "<CLM/>",
components: {CLM},
variableToPass: externalVar
});
The component I am initializing here is defined like this (getting back variableToPass in data):
<template src="./template-l-m.html"></template>
<script>
import draggable from 'vuedraggable';
export default {
name: 'leftmenu',
components: {
draggable
},
data () {
return {
jsonObject: this.variableToPass,
}
},
[ ... ]
</script>
But then , when I am trying to use this.jsonObject, it says that it's undefined. What am I doing wrong ?
If i understand you correctly you want to use props to pass data to child components
Dynamically bind a prop attribute on child component element using :variable="variableToPass"
var externalVar = "hello world"
const leftmenu = new Vue({
el: "#left-menu",
template: "<CLM :variable='variableToPass'/>",
components: {CLM},
data: {
variableToPass: externalVar
}
});
Define a props option in the child component
<template src="./template-l-m.html"></template>
<script>
import draggable from 'vuedraggable';
export default {
name: 'leftmenu',
components: {
draggable
},
props: ['variable'],
data () {
return {
jsonObject: this.variable,
}
},
[ ... ]
</script>
Use data.
var externalVar = "hello world"
const leftmenu = new Vue({
el: "#left-menu",
template: "<CLM/>",
components: {CLM},
data: {
variableToPass: externalVar
}
});
That way you can access your variable like this this.$data.variableToPass
Use $options
in child component
mounted() {
console.log(this.$parent.$options.variableToPass) // hello world
this.jsonObject = this.$parent.$options.variableToPass
}

vuex: count is not defined in vue component file

Just started to integrate vuex to my laravel application, going through with the sample counter application in the official vuex docs.
js/components/calculate.vue
<template>
<div>
<p>{{ count }}</p>
<p>
<button #click="increment">+</button>
<button #click="decrement">-</button>
</p>
</div>
</template>
<script >
import store from '../app.js'
export default {
computed: {
count () {
return store.state.count
}
},
methods: {
increment () {
store.commit('increment')
},
decrement () {
store.commit('decrement')
}
}
}
</script>
js/app.js
const calculate = Vue.component('calculate', require('./components/calculate.vue'));
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
});
const app = new Vue({
el: '#app'
});
Using webpack build
I got store.state.count not defined error in this line return store.count.
If you are creating your store in app.js file, (since you haven't mentioned) you need to add
const calculate = Vue.component('calculate', require('./components/calculate.vue'));
Vue.use(Vuex) // this line
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
});
const app = new Vue({
el: '#app'
});
This goes without saying that you need to import Vuex in this file.
after which you need to modify, this part of your code:
const app = new Vue({
el: '#app'
});
to
const app = new Vue({
el: '#app',
store // if you're vue-cli/babel else use store: store
});
And in js/components/calculate.vue
<template>
<div>
<p>{{ count }}</p>
<p>
<button #click="increment">+</button>
<button #click="decrement">-</button>
</p>
</div>
</template>
<script >
// import store from '../app.js' this import is not required
export default {
computed: {
count () {
return this.$store.state.count
}
},
methods: {
increment () {
this.$store.commit('increment')
},
decrement () {
this.$store.commit('decrement')
}
}
}
</script>
The import is not required as the store is supposed to be accessible to each component of the vue instance, to access it, you just need to have access to $store property of the vue instance which in your component is this (as long as the context doesn't change by means of a callback or something similar.)

Categories

Resources