Creating a global variable in Vue.js - javascript

In Vue.js 0.12, it was easy enough to pass a variable from the root component all the way down to its children: you just use inherit: true on any component that requires the data of its parent.
Vue.js 1.0 removed the ability to use inherit: true. Understandably, you really shouldn't be inheriting everything everywhere all the time.
This is something of a problem upgrading from 0.12 to 1.0 however. I've got components littered with inherit, most only so that it can get the important config variable baseurl from the root component. Such a variable should definitely by known by any component that has a link in it.
Link
I could pass the prop for config to every single component, but that really seems redundant and clunky.
<c0 :config="{baseurl: 'example.com'}"></c0>
<c1 :config="config"></c1>
<c2 :config="config"></c2>
<c3 :config="config"></c3>
Any good way to create a global scope for all components?

This might not be the best solution out there, but it works and isn't too much trouble. I just set a getter prototype method.
Vue.prototype.$config = function (key) {
var config = this.$root.$get('config');
if (config) {
return config[key];
}
return '';
}
Link
Accessible by any component.

If you're using something like webpack in your Vue setup, then you can share a variable across your project with ES6 modules / import statements.
main.js:
import Vue from 'vue'
import config from './config.js'
import thing from './thing.js'
config.setColor('green');
var vm = new Vue({
el: "#app",
components: { thing },
template: '<thing></thing>'
});
config.js:
var color = 'blue';
export default {
getColor: function () {
return color;
},
setColor: function (val) {
color = val;
}
}
thing.js:
import config from './config.js'
export default {
name: 'thing',
template: '<div>thing</div>',
created: function () {
console.log(config.getColor());
// green
}
}

It's very easy. All you need to do is to set it in the "data" area in your "main.js" file. I am not sure why all other answers are complicating things.
new Vue({
data: {
API: "http://localhost:8080"
},
router,
vuetify,
render: h => h(App)
}).$mount('#app')
And then in any component, you access it through "this.$root"
console.log(this.$root.API);

take a look at : vue-config
npm install vue-config
const vueConfig = require('vue-config')
const configs = {
API: 'http://localhost:6003' // It's better to require a config file
}
Vue.use(vueConfig, configs)
// A param named `$config` will be injected in to every
// component, so in your component, you can get config easily
const API = this.$config.API
vuex may be too heavy for defining constant. however, once your problem solved, you must take a look at Vuex.

Related

How to link the imported dependencies of module created by vm.SourceTextModule to it?

Let's say we are creating a module called app by constructing a new vm.SourceTextModule object:
const context = {
exports: {},
console, // custom console object
};
const sandbox = vm.createContext(context);
const app = new vm.SourceTextModule(
`import path from 'path';
console.log(path.resolve('./src'));`,
{
context: sandbox,
}
);
According to the Node.js documentation to obtain the default export from path module we should "link" the imported dependencies of app module to it.
To achieve this we should pass linker callback to app.link method:
async function linker(specifier, referencingModule) {
// the desired logic...
}
await app.link(linker);
How to implement linker function properly so that we could import path module in newly created app module and use it:
await app.evaluate(); // => /home/user/Documents/project/src
P.S. We are using TypeScript, so I checked if we have installed types for path package.
package.json:
"#types/node": "^17.0.31",
I found https://github.com/nodejs/node/issues/35848 where someone posted a code snippet.
From there I've adapted the following linker callback:
const imports = new Map();
async function linker(specifier, referencingModule) {
if (imports.has(specifier))
return imports.get(specifier);
const mod = await import(specifier);
const exportNames = Object.keys(mod);
const imported = new vm.SyntheticModule(
exportNames,
() => {
// somehow called with this === undefined?
exportNames.forEach(key => imported.setExport(key, mod[key]));
},
{ identifier: specifier, context: referencingModule.context }
);
imports.set(specifier, imported);
return imported;
}
The code snippet from the GitHub issue didn't work for me on Node 18.7.0 as is, because the evaluator callback passed to the constructor of SyntheticModule is somehow called with this set to undefined. This may be a Node bug.
I also cached the imported SyntheticModules in a Map because if they have internal state, creating a new SyntheticModule every time will reset that state.

Javascript const object syntax - what is this doing? (using in a Vue script)

What is the following statement doing, and why use const vs var?
const { SearchIcon } = myApp.icons;
For context, I am learning Vue and new to Javascript. I saw this in an example. The SearchIcon is an icon that is being imported from heroicons, MyApp.icons is being defined in a different script file like this:
window.MyApp = {
app: null,
icons: {},
...
Looks like your issue is that you're storing MyApp under window, but then trying to access it directly. You've also got a capital M in the definition, and a lowercase when your accessing it.
Here's a working example:
window.MyApp = {
app: null,
icons: {
SearchIcon : 'Hello!'
},
};
const { SearchIcon } = window.MyApp.icons;
console.log(SearchIcon);
For more info, see the docs on object destructuring.
Hope that helps :)

Inject dependency into Vuex module

Maybe someone has had a similar situation and can help with this:
Very simplistic view:
I have a Vuex module that I am using with two different stores
This module uses a "normalizer" function that maps data from an API into the format that's used by the components consuming the data
The components in the two stores need the data in slightly different formats
My current idea is to have two different "normalizer" functions that share the same "interface" but produce slightly different outcomes, and "inject" them as a dependency into the module before attaching it to the store:
Existing code - "fixed" normalizer function (plain old Vuex module):
// module.js
import { normalize } from './utils';
const actions = {
getData({ commit }) {
MyApi.getData().then((data) => {
commit('setData', data.map(normalize));
});
},
};
// ...
export default {
actions,
// ...
}
// my-store.js
import MyModule from './my-module';
export default new Vuex.Store({
modules: {
'my-module': MyModule,
},
// ...
});
New code that can accept different functions:
// module.js
export default (inject) => {
const { normalize } = inject;
const actions = {
getData({ commit }) {
MyApi.getData().then((data) => {
commit('setData', data.map(normalize));
});
},
};
//...
return {
actions,
// ...
};
}
// store-type-1.js
import getMyModule from './my-module';
import normalizerType1 from './normalizer-type-1';
export default new Vuex.Store({
modules: {
'my-module': getMyModule({ normalize: normalizerType1 }),
},
// ...
})
Thing is, something about this implementation feels like a sore thumb, and I'm pretty sure there are smarter ways of doing this, I'm just not smart enough to think of them.
I can provide more details about what this "normalizer" function is expected to do, maybe I'm barking up the wrong tree.
What's the industry standard for this sort of thing?
Thank you!

Change moment locale on runtime globally (make Vue.prototype.moment reactive)

I am importing moment into Vue like so:
import moment from 'moment-timezone/builds/moment-timezone-with-data-2012-2022';
Vue.prototype.moment = moment;
Then in my created(), I setup the languages I want.
Bear in mind that this is working with refresh, but not on runtime.
export default new Vue({
render: h => h(BaseApp),
mounted() {
let supportedLanguages = ['tr', 'etc'];
supportedLanguages.forEach((val) => {
moment.locale(val, {
months: this.$i18n.messages[val]._months_,
monthsShort: this.$i18n.messages[val]._months_short_,
monthsParseExact: true,
weekdays: this.$i18n.messages[val]._weekdays_,
weekdaysShort: this.$i18n.messages[val]._weekdays_short_,
weekdaysMin: this.$i18n.messages[val]._weekdays_min_,
weekdaysParseExact: true,
});
})
},
methods: {
changeLocale(lang) {
this.moment.locale(lang);
}
}
})
However, in my console if I try $vm0.moment.locale() it returns back the correct locale
In all my components, I'm using it in template
<h1>{{ day.format('ddd') }}</h1>
The problem is, when I try to change moment's locale, it doesn't change in every component's template. Is there a way to force refresh in all templates. (I tried $vm.$forceUpdate() in root)
How can I make moment reactive?
Globally changing the locale on Moment.js does not change the locale on existing moment instances. Even if it did, it would not trigger an update, as Vue is not keeping track of those changes.
I would suggest a different solution using a global mixin, with a method that sets the locale locally on a moment instance.
import Vue from 'vue';
Vue.mixin({
methods: {
formatDate (moment, format) {
return moment
.locale(this.$root.locale)
.format(format);
},
},
});
export default new Vue({
render: h => h(BaseApp),
data: {
locale: 'en'
},
methods: {
changeLocale(locale) {
this.locale = locale;
}
}
});
Your inline template code remains equally readable. However, while fixing your problem, this is also a much more flexible solution that does not care where a moment instance is coming from.
<h1>{{ formatDate(day, 'ddd') }}</h1>
Two-way data binding by VueJS. It's not moment problem.
var parent = new Vue({
data: {
a: 1
}
})
// $addChild() is an instance method that allows you to
// programatically create a child instance.
var child = parent.$addChild({
inherit: true,
data: {
b: 2
}
})
console.log(child.a) // -> 1
console.log(child.b) // -> 2
parent.a = 3
console.log(child.a) // -> 3

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.

Categories

Resources