How to use variable globally in vuejs - javascript

So I have a vuejs project and want to use some variable (globally) in any of my components (Variable will not change its value).
I created a variable called STEPS in somefile.js and imported it from components where I want to use.
// somefile.js
export const STEPS = {
PROJECT: 'project',
USER: 'user',
SUMMARY: 'summary',
}
// maybe freeze object here because value will not be changed
// component1.vue
import { STEPS } from 'somefile.js'
export default {
methods: {
someMethod(value) {
if (value === STEPS.PROJECT) {
// do something
}
}
}
}
// component2.vue
import { STEPS } from 'somefile.js'
export default {
methods: {
someMethod(value) {
if (value === STEPS.USER) {
// do something
}
}
}
}
So this actually works? I don't get any errors or anything. But I'm just wondering .. I am not sure if it's okay to use it like this? Just making a .js file and import it anywhere in your code and use it?
So..
I found 2 ways how people use global variables in vuejs.
using instance properties
https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html#Base-Example
Vue.prototype.$STEPS = {
PROJECT: 'project',
USER: 'user',
SUMMARY: 'summary',
}
created() {
console.log(this.$STEPS)
}
Using mixins
https://v2.vuejs.org/v2/guide/mixins.html
var mixin = {
data: function () {
return {
message: 'hello',
foo: 'abc'
}
}
}
new Vue({
mixins: [mixin],
data: function () {
return {
message: 'goodbye',
bar: 'def'
}
},
created: function () {
console.log(this.$data)
// => { message: "goodbye", foo: "abc", bar: "def" }
}
})
So my question is is it okay to use how I used global variable? Just making a variable in javascript file and importing it..
Or should I change it to using instance properties or mixins?

Yes, the way you do it is totally fine. If you want to manage your data centrally where is can be accessed by all components (and can even be changed across all of them simultaneously), you could also have a look at Vuex. Here is also a great (but maybe outdated) tutorial on Vuex.

Related

How to use enums (or const) in VueJS?

I feel like an idiot for having to ask about something so seemingly simple, but I'm trying to figure out how to use "enums" in VueJS. Currently, in a file called LandingPage.js I have this bit of code:
const Form = {
LOGIN: 0,
SIGN_UP: 1,
FORGOT_PASSWORD: 2,
};
function main() {
new Vue({
el: "#landing-page",
components: {
LoginForm,
WhoIsBehindSection,
WhatIsSection,
Form,
},
data () {
return {
form: Form.LOGIN,
};
},
template: `
<div>
<LoginForm v-if="form === Form.LOGIN"></LoginForm>
<WhatIsSection></WhatIsSection>
<WhoIsBehindSection></WhoIsBehindSection>
</div>
`
});
}
It is the conditional v-if="form === Form.LOGIN" that is failing with the error messages:
Property or method "Form" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
Cannot read property 'LOGIN' of undefined
Just so you guys know without the conditional everything is working, and if I were to put this bit in the template
<p>{{ form }}</p>
it will print 0 on the screen. Though, putting this in the template
<p>{{ Form.LOGIN }}</p>
Will not result in it printing 0 on the screen. So I just cannot for the life of me figure out why it will not accept Form.LOGIN.
The Answer
I did add it to components, but never did I think of adding it to data. Happy that it's working now. :)
data () {
return {
form: Form.LOGIN,
Form, // I had to add this bit
};
},
Thank you MarcRo 👍
If you are using Vue in Typescript, then you can use:
import { TernaryStatus } from '../enum/MyEnums';
export default class MyClass extends Vue {
myVariable: TernaryStatus = TernaryStatus.Started;
TernaryStatus: any = TernaryStatus;
}
and then in Template you can just use
<div>Status: {{ myVariable == TernaryStatus.Started ? "Started It" : "Stopped it" }}</div>
You can use https://stackoverflow.com/a/59714524/3706939.
const State = Object.freeze({ Active: 1, Inactive: 2 });
export default {
data() {
return {
State,
state: State.Active
};
},
methods: {
method() {
return state === State.Active;
}
}
}
You only have access to properties of the Vue instance in your template. Just try accessing window or any global in your template, for example.
Hence, you can access {{ form }} but not {{ Form.LOGIN }}.
A wild guess is that it has something to do with how Vue compiles, but I don't know enough about the internals to answer this.
So just keep declaring all the properties you wish to use in your template in your Vue instance (usually as data).
You can enclose enum into class. All your data, the state, the enum variants would be in one place. The same about behaviours, so you will call form.isLogin() rather than form === Form.LOGIN and form.setLogin() rather than form = Form.Login.
The class to generate enums:
class Fenum {
constructor(start, variants) {
this.state = start;
variants.forEach(value => {
const valueC = value.charAt(0).toUpperCase() + value.slice(1);
this['is' + valueC] = () => this.state === value;
this['set' + valueC] = () => this.state = value;
})
}
}
Example of usage:
function main() {
new Vue({
el: "#landing-page",
components: {
LoginForm,
WhoIsBehindSection,
WhatIsSection,
Form,
},
data () {
return {
form: new Fenum("login", ["login", "signUp", "forgotPassword"]),
};
},
template: `
<div>
<LoginForm v-if="form.isLogin()"></LoginForm>
<WhatIsSection></WhatIsSection>
<WhoIsBehindSection></WhoIsBehindSection>
</div>
`
});
}
Vue observe nested objects, so each call of a set method (from.setLogin(), form.setSignUp(), ...) will trigger updates of the component as it should be.
The generated object from this example:
You can use $options instead of $data https://vuejs.org/v2/api/#vm-options
You can use Proxy to create object which throw runtime errors if someone will read non-defined value or try to add new value - here is createEnum (and use it in data() section)
function createEnum(name,obj) {
return new Proxy(obj, {
get(target, property) {
if (property in target) return target[property];
throw new Error(`ENUM: ${name}.${property} is not defined`);
},
set: (target, fieldName, value) => {
throw new Error(`ENUM: adding new member '${fieldName}' to Enum '${name}' is not allowed.`);
}
});
}
// ---------------
// ----- TEST ----
// ---------------
const Form = createEnum('Form',{
LOGIN: 0,
SIGN_UP: 1,
FORGOT_PASSWORD: 2,
});
// enum value exists
console.log(Form.LOGIN);
// enum value not exists
try{ console.log(Form.LOGOUT) } catch(e){ console.log(e.message)}
// try to add new value
try{ Form.EXIT = 5 } catch(e){ console.log(e.message)}
for string-like Enums where values are equal to keys you can use following helper
export function createEnumArr(name='', values=[]) {
let obj = {};
values.forEach(v => obj[v]=v);
return createEnum(name,obj);
}
const Form = createEnumArr('Form',[
"LOGIN",
"SIGN_UP",
"FORGOT_PASSWORD",
]);
The easiest way!
in main.js
const enumInfo = {
SOURCE_TYPE: {
WALLET: 1,
QR: 2
}
}
Vue.prototype.enumInfo = enumInfo
index.vue
{{enumInfo}}
For 2022 and beyond you should probably be using Vue 3 and Typescript.
The easiest way to use an enum is to map it to string values and then simply return it from your setup function.
<template>
...
<div v-if="mode == DarkModes.DARK">
do something for dark mode
</div>
...
</template>
<script lang="ts">
enum DarkModes {
BRIGHT = 'bright',
DARK = 'dark',
}
export default defineComponent({
name: 'MyDarkOrBrightComponent',
setup() {
const mode = ref(DarkModes.BRIGHT);
...
return {
mode,
DarkModes, // <- PASS YOUR ENUM HERE!
}
}
});
</script>
And if you're using the new <script setup> functionality it's just as easy ... all top level imports are automatically accessible from the template (if you want to put your enum in a separate file).
I've this problem, too.
Here my solution, just put this in the first line:
<script setup>
const Form = {
LOGIN: 0,
SIGN_UP: 1,
FORGOT_PASSWORD: 2,
};
</script>

How to access a Vue plugin from another plugins (using Vue.prototype)?

I'm trying to write a Vue plugin that's a simple abstraction to manage auth state across my app. This will need to access other Vue plugins, namely vuex, vue-router and vue-apollo (at the moment).
I tried extending Vue.prototype but when I try to access the plugin's properties how I would normally - eg. this.$apollo - I get the scope of the object, and therefore an undefined error. I also tried adding vm = this and using vm.$apollo, but this only moves the scope out further, but not to the Vue object - I guess this is because there is no instance of the Vue object yet?
export const VueAuth = {
install (Vue, _opts) {
Vue.prototype.$auth = {
test () {
console.log(this.$apollo)
}
}
}
}
(The other plugins are imported and added via. Vue.use() in the main app.js)
Alternatively, I tried...
// ...
install (Vue, { router, store, apollo })
// ...
but as a novice with js, I'm not sure how this works in terms of passing a copy of the passed objects, or if it will mutate the originals/pass by ref. And it's also very explicit and means more overhead if my plugin is to reach out to more plugins further down the line.
Can anyone advise on a clean, manageable way to do this? Do I have to instead alter an instance of Vue instead of the prototype?
In the plugin install function, you do not have access to the Vue instance (this), but you can access other plugins via the prototype. For example:
main.js:
Vue.use(Apollo)
Vue.use(VueAuth) // must be installed after vue-apollo
plugin.js:
export const VueAuth = {
install (Vue) {
Vue.prototype.$auth = {
test () {
console.log(Vue.prototype.$apollo)
}
}
}
}
I found a simple solution for this issue:
In plugin installer you need to add value to not just prototype, but Vue itself to be able to use it globally.
There is a code example:
Installer:
import apiService from "../services/ApiService";
// Service contains 'post' method
export default {
install(Vue) {
Vue.prototype.$api = apiService;
Vue.api = apiService;
}
};
Usage in other plugin:
import Vue from "vue";
...
const response = await Vue.api.post({
url: "/login",
payload: { email, password }
});
Usage in component:
const response = await this.$api.post({
url: "/login",
payload: { email, password }
});
I'm not sure if that's a good solution, but that made my scenario work perfectly.
So, I got around this by converting my property from a plain ol' object into a closure that returns an object, and this seems to have resolved my this scoping issue.
Honestly, I've jumped into Vue with minimal JS-specific knowledge and I don't fully understand how functions and the likes are scoped (and I'm not sure I want to look under that rock just yet......).
export const VueAuth = {
install (Vue, opts) {
Vue.prototype.$auth = function () {
let apollo = this.$apolloProvider.defaultClient
let router = this.$router
return {
logIn: function (email, password) {
apollo.mutate({
mutation: LOGIN_MUTATION,
variables: {
username: email,
password: password,
},
}).then((result) => {
// Result
console.log(result)
localStorage.setItem('token', result.data.login.access_token)
router.go(router.currentRoute.path)
}).catch((error) => {
// Error
console.error('Error!')
console.error(error)
})
},
logOut: function () {
localStorage.removeItem('token')
localStorage.removeItem('refresh-token')
router.go()
console.log('Logged out')
},
}
}
It's a rudimental implementation at the moment, but it'll do for testing.

What is the meaning of this strange syntax [SOME_MUTATION] (state) for function definition in JavaScript?

In Vue I see code like:
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
and
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// we can use the ES2015 computed property name feature
// to use a constant as the function name
[SOME_MUTATION] (state) {
// mutate state
}
}
})
Where the syntax [SOME_MUTATION] (state) comes from? Is [SOME_MUTATION, ANOTHER] (state) also valid syntax? Why SOME_MUTATION (state) (which is valid function syntax) is not used?
How in large Vue project, putting all export const SOME_MUTATION = 'SOME_MUTATION' defintions in separate file really help instead of putting in same file (store.js) ?
In this line
import { SOME_MUTATION } from './mutation-types'
where you are importing the types into SOME_MUTATION, it has a string value. Now to define a function with this name inside an object you need to do
{
[SOME_MUTATION](state) {
}
}
Would be same as (if SOME_MUTATION has a value say changeState)
{
changeState: function(state){
}
}
And state is by default passed as an argument in mutations.
Read more here
We prefer this type of architecture in a Vue application so as to avoid name conflicts in defining Mutations and Actions and Getters across multiple components.
It's a javascript syntax for defining an object key programatically/dynamically.
for example, you have a variable:
var myFunctionName = "getUsers";
you can actually create a function inside an object using the value of a variable as the function name.
So for example,
{
[myFunctionName]() {
// do something
}
}
will actually become:
ES6:
{
getUsers() {
// do something
}
}
or ES5:
{
getUsers: function() {
// do something
}
}
This is not limited to creating functions within an object, but it can be applied to any property within your object.
For example:
let myPropertyName = 'user';
{
[myPropertyName]: {
name: 'Hello',
age: 10
}
}
will become:
{
user: {
name: 'Hello',
age: 10
}
}

Pass data to component in VueJS

I'm using VueJS and I need to pass Firebase Authentication user data (JSON) from the component App.vue to another component called Details.vue to display the name of that logged in user.
App.vue:
export default {
name: 'app',
beforeCreate: function () {
Firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.user = user
// user is logged in now -> pass data to App.vue component here
[...]
What is a good way to do this?
(I'm new to VueJS and tried a couple of suggested answers like using "ref" <add ref="details"></add> - unfortunately none of them worked for me)
The long-run solution is Vuex.
If the user is the only global property you want to have, you can create a data property at the root and access from any component using this.$root.propName.
Example:
// probably main.js file
new Vue({
el: "#app",
data: {user: null}, // added this
components: { App },
template: "<App/>"
});
App.js:
export default {
name: 'app',
beforeCreate: function () {
Firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.$root.user = user; // set to this.$root.user instead of this.user
In any other component:
export default {
name: 'something',
methods: {
printUser() { console.log(this.$root.user); }
},
// ...
That's a quick solution. Don't abuse the $root data. Having user is like having a global variable. The more code uses it, the harder it becomes to debug. Vuex handles that by having all changes concentrated inside mutations. In summary: if you find yourself needing more $root properties, go straight to Vuex.

How can I access this.$route from within vue-apollo?

I'm constructing a GraphQL query using vue-apollo and graphql-tag.
If I hardcode the ID I want, it works, but I'd like to pass the current route ID to Vue Apollo as a variable.
Does work (hardcoded ID):
apollo: {
Property: {
query: PropertyQuery,
loadingKey: 'loading',
variables: {
id: 'my-long-id-example'
}
}
}
However, I'm unable to do this:
Doesn't work (trying to access this.$route for the ID):
apollo: {
Property: {
query: PropertyQuery,
loadingKey: 'loading',
variables: {
id: this.$route.params.id
}
}
}
I get the error:
Uncaught TypeError: Cannot read property 'params' of undefined
Is there any way to do this?
EDIT: Full script block to make it easier to see what's going on:
<script>
import gql from 'graphql-tag'
const PropertyQuery = gql`
query Property($id: ID!) {
Property(id: $id) {
id
slug
title
description
price
area
available
image
createdAt
user {
id
firstName
lastName
}
}
}
`
export default {
name: 'Property',
data () {
return {
title: 'Property',
property: {}
}
},
apollo: {
Property: {
query: PropertyQuery,
loadingKey: 'loading',
variables: {
id: this.$route.params.id // Error here!
}
}
}
}
</script>
You can't have access to "this" object like that:
variables: {
id: this.$route.params.id // Error here!
}
But you can like this:
variables () {
return {
id: this.$route.params.id // Works here!
}
}
Readimg the documentation( see Reactive parameters section) of vue-apollo you can use vue reactive properties by using this.propertyName. So just initialize the route params to a data property as then use it in you apollo object like this
export default {
name: 'Property',
data () {
return {
title: 'Property',
property: {},
routeParam: this.$route.params.id
}
},
apollo: {
Property: {
query: PropertyQuery,
loadingKey: 'loading',
// Reactive parameters
variables() {
return{
id: this.routeParam
}
}
}
}
}
While the accepted answer is correct for the poster's example, it's more complex than necessary if you're using simple queries.
In this case, this is not the component instance, so you can't access this.$route
apollo: {
Property: gql`{object(id: ${this.$route.params.id}){prop1, prop2}}`
}
However, you can simply replace it with a function, and it will work as you might expect.
apollo: {
Property () {
return gql`{object(id: ${this.$route.params.id}){prop1, prop2}}`
}
}
No need for setting extra props.

Categories

Resources