What is the best way to declare global variables in Vue.js? - global-variables

I need access to my hostname variable in every component.
Is it a good idea to put it inside data?
Am I right in understanding that if I do so, I will able to call it everywhere with this.hostname?

As you need access to your hostname variable in every component, and to change it to localhost while in development mode, or to production hostname when in production mode, you can define this variable in the prototype.
Like this:
Vue.prototype.$hostname = 'http://localhost:3000'
And $hostname will be available in all Vue instances:
new Vue({
beforeCreate: function () {
console.log(this.$hostname)
}
})
In my case, to automatically change from development to production, I've defined the $hostname prototype according to a Vue production tip variable in the file where I instantiated the Vue.
Like this:
Vue.config.productionTip = false
Vue.prototype.$hostname = (Vue.config.productionTip) ? 'https://hostname' : 'http://localhost:3000'
An example can be found in the docs:
Documentation on Adding Instance Properties
More about production tip config can be found here:
Vue documentation for production tip

a vue3 replacement of this answer:
// Vue3
const app = Vue.createApp({})
app.config.globalProperties.$hostname = 'http://localhost:3000'
app.component('a-child-component', {
mounted() {
console.log(this.$hostname) // 'http://localhost:3000'
}
})

Warning: The following answer is using Vue 1.x. The twoWay data mutation is removed from Vue 2.x (fortunately!).
In case of "global" variables—that are attached to the global object, which is the window object in web browsers—the most reliable way to declare the variable is to set it on the global object explicitly:
window.hostname = 'foo';
However form Vue's hierarchy perspective (the root view Model and nested components) the data can be passed downwards (and can be mutated upwards if twoWay binding is specified).
For instance if the root viewModel has a hostname data, the value can be bound to a nested component with v-bind directive as v-bind:hostname="hostname" or in short :hostname="hostname".
And within the component the bound value can be accessed through component's props property.
Eventually the data will be proxied to this.hostname and can be used inside the current Vue instance if needed.
var theGrandChild = Vue.extend({
template: '<h3>The nested component has also a "{{foo}}" and a "{{bar}}"</h3>',
props: ['foo', 'bar']
});
var theChild = Vue.extend({
template: '<h2>My awesome component has a "{{foo}}"</h2> \
<the-grandchild :foo="foo" :bar="bar"></the-grandchild>',
props: ['foo'],
data: function() {
return {
bar: 'bar'
};
},
components: {
'the-grandchild': theGrandChild
}
});
// the root view model
new Vue({
el: 'body',
data: {
foo: 'foo'
},
components: {
'the-child': theChild
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.16/vue.js"></script>
<h1>The root view model has a "{{foo}}"</h1>
<the-child :foo="foo"></the-child>
In cases that we need to mutate the parent's data upwards, we can add a .sync modifier to our binding declaration like :foo.sync="foo" and specify that the given 'props' is supposed to be a twoWay bound data.
Hence by mutating the data in a component, the parent's data would be changed respectively.
For instance:
var theGrandChild = Vue.extend({
template: '<h3>The nested component has also a "{{foo}}" and a "{{bar}}"</h3> \
<input v-model="foo" type="text">',
props: {
'foo': {
twoWay: true
},
'bar': {}
}
});
var theChild = Vue.extend({
template: '<h2>My awesome component has a "{{foo}}"</h2> \
<the-grandchild :foo.sync="foo" :bar="bar"></the-grandchild>',
props: {
'foo': {
twoWay: true
}
},
data: function() {
return { bar: 'bar' };
},
components: {
'the-grandchild': theGrandChild
}
});
// the root view model
new Vue({
el: 'body',
data: {
foo: 'foo'
},
components: {
'the-child': theChild
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.16/vue.js"></script>
<h1>The root view model has a "{{foo}}"</h1>
<the-child :foo.sync="foo"></the-child>

I strongly recommend taking a look at Vuex, it is made for globally accessible data in Vue.
If you only need a few base variables that will never be modified, I would use ES6 imports:
// config.js
export default {
hostname: 'myhostname'
}
// .vue file
import config from 'config.js'
console.log(config.hostname)
You could also import a json file in the same way, which can be edited by people without code knowledge or imported into SASS.

For any Single File Component users, here is how I set up global variable(s)
Assuming you are using Vue-Cli's webpack template
Declare your variable(s) in somewhere variable.js
const shallWeUseVuex = false;
Export it in variable.js
module.exports = { shallWeUseVuex : shallWeUseVuex };
Require and assign it in your vue file
export default {
data() {
return {
shallWeUseVuex: require('../../variable.js')
};
}
}
Ref: https://v2.vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch

In vue cli-3 You can define the variable in main.js like
window.basurl="http://localhost:8000/";
And you can also access this variable in any component by using
the the
window.basurl

A possibility is to declare the variable at the index.html because it is really global. It can be done adding a javascript method to return the value of the variable, and it will be READ ONLY.
An example of this solution can be found at this answer:
https://stackoverflow.com/a/62485644/1178478

Related

Default value is not passed to prop

The code below should display my prop default value ("test") since it received no other value. Yet it displays nothing. Why not?
<template>
<div>
{{ propValue }}
</div>
</template>
<script setup lang="ts">
import { defineProps } from "vue"
const props = defineProps<{
myProp: {
type: string
default: "test"
}
}>()
const propValue = props.myProp
</script>
There are two reasons why.
When you use props with TypeScript you lose the ability to set default values, but it can be fixed using the withDefaults macro (you also need to separately declare a Props interface). Side note: I would not use the alternative experimental "Reactivity Transform" solution noted in the docs (it's being removed soon)
export interface Props {
myProp?: string;
}
const props = withDefaults(defineProps<Props>(), {
myProp: 'test'
});
This one isn't actually strictly necessary, but props is a reactive object, and when you assign a single property of props to a new variable, the reactivity breaks. In order to maintain reactivity and stay synced with any future changes to props, use the toRef function
const propValue = toRef(props, 'myProp');
and be sure to import it: import { defineProps, toRef } from 'vue';

Is there a way to use a prop as the site-key for vue-recaptcha?

I have a Form component in Vue where I import the vue-recaptcha like this:
<template>
... Contains the form and button that triggers onSubmit function
</template>
<script>
import { VueReCaptcha } from 'vue-recaptcha-v3';
Vue.use(VueReCaptcha, {
siteKey: "hard-coded site-key here",
loaderOptions: {
useRecaptchaNet: true,
},
});
export default {
methods: {
async onSubmit(e) {
// Uses the recapatcha and handles errors/success etc.
},
},
};
...
This works since the value for site-key is hard-coded.
However, I wish to be able to pass the site-key as a prop to the Form component and then use this as the site-key.
I tried something as bold as simply creating a prop in the Form component and passing it in as the site-key when setting the vue-recaptcha options, like this:
<script>
import { VueReCaptcha } from 'vue-recaptcha-v3';
Vue.use(VueReCaptcha, {
siteKey: this.siteKey,
loaderOptions: {
useRecaptchaNet: true,
},
});
export default {
props: {
siteKey: String,
},
...
</script>
This does not work because this.siteKey is undefined, as expected. However, is there a way to set the site-key value as the prop siteKey? Maybe there is a way to set the vue-recaptcha plugin options inside the component where this.siteKey isn't undefined, for example in mounted()?
Have you tried to provide the Key to the Form over prop?
<my-form siteKey="<My Site Key>" ...
or
<my-form site-key="<My Site Key>" ...
Pay attention:
You should be careful with components and prop's naming & using, since there are DOM
Template Parsing Caveats

Vue 3 with Tailwind using v-bind:class shows classes in html element but not render while style works correctly

I am new to Vue and I'm trying to bind multiple classes in a v-for loop from a const array of object imported from a file.js.
But the trick I'm trying is to import const and than return classes from method that evaluates one property of object looped.
I've tried all ways, methods, computed, setup, onMounted, beforeMount, but even if i can see my classes in html they aren't rendered in styles section of DevTools.
The only way that works is to v-bind:style instead of class. Or just put exact classes in my const array object as a property but I prefer to avoid this.
It seems to save something in cache, but i have tried to delete and to lunch application in hidden mode but it won't works
Is there someone who can help me to understand and maybe to resolve?
Thanks in advance
this is my actual code:
<template>
<div id="cv" class="tp3-flex md:tp3-grid md:tp3-grid-cols-[repeat(27,_minmax(0,_1fr))] md:tp3-grid-rows-[repeat(6,_minmax(0, 5rem))] tp3-justify-center tp3-content-center tp3-justify-items-center tp3-mx-auto tp3-p-2 tp3-bg-cyan-500 tp3-text-blue-50">
<div v-for="(softSkill, index) in softSkills" :key="`softSkill-${index}`"
class="tp3-flex tp3-w-20 tp3-h-20 -tp3-rotate-45 tp3-rounded-full tp3-rounded-tr-none tp3-justify-center tp3-items-center tp3-bg-slate-400 tp3-opacity-70 tp3-mb-4 tp3-mt-4 tp3-shadow-md tp3-overflow-hidden"
v-bind:class="posCols(softSkill)">
<div class="tp3-rotate-45">
<span v-html="softSkill.text"></span>
</div>
</div>
</div>
</template>
<script>
import {softSkills} from "#/assets/skills/softSkills";
export default {
name: "ComponentSoftSkills",
data(){
return{
softSkills: null
}
},
beforeMount() {
this.softSkills = softSkills;
},
methods: {
posCols(softSkill){
console.log(softSkill);
return ' tp3-col-start-['+softSkill.col+'] tp3-col-end-['+(softSkill.col+1)+']';
}
}
}
</script>
<style lang="css" scoped>
</style>
and my file.js is:
export const softSkills = [
{text:`skill 1`, col:1, row:1},
{text:`skill 2`, col:5, row:1},
{text:`skill 3`, col:2, row:2},
{text:`skill 4`, col:15, row:1},
]
I have a suspicion that this might be due to your tailwind setup.
Because the classes are assigned dynamically and tailwind (depending on the configuration) is only making classes available that it can find during compilation. So the classes, even though you see them populated correctly, are not made available through tailwind. simply put, when tailwind scans the code, it doesn't recognize md:tp3-grid-cols-[repeat(27,_minmax(0,_1fr))] or tp3-col-start-[${softSkill.col}] as a valid class name and does not generate the class for it.
Assuming this is the issue and not knowing the exact version on configuration can't give an exact solution, but here are some tips for it.
Instead of using dynamic class names, define all the class names and assign dynamically
so instead of using tp3-col-start-[${softSkill.col}] tp3-col-end-[${(softSkill.col+1)}]
you could make sure all possible classes are clear and accessible by the tailwind parser:
let colClass = `tp3-col-start-[0] tp3-col-end-[1]`;
if(softSkill.col === 1) colClass = "tp3-col-start-[1] tp3-col-end-[2]";
if(softSkill.col === 2) colClass = "tp3-col-start-[2] tp3-col-end-[3]";
if(softSkill.col === 3) colClass = "tp3-col-start-[3] tp3-col-end-[4]";
if(softSkill.col === 4) colClass = "tp3-col-start-[4] tp3-col-end-[5]";
if(softSkill.col === 5) colClass = "tp3-col-start-[5] tp3-col-end-[6]";
// ...etc
this is obviously very verbose, but the classes are clearly defined in the code, so tailwind can find them when scanning your code.
Safelisting classes
using safelisting of classes is another option. Instead of having the code in your js, you would have it in the configuration
// tailwind.config.js
module.exports = {
// ...other stuff
safelist: [
'tp3-col-start-[0]',
'tp3-col-start-[1]',
'tp3-col-start-[2]',
'tp3-col-start-[3]',
'tp3-col-start-[4]',
'tp3-col-start-[5]',
// ...etc
'tp3-col-end-[1]',
'tp3-col-end-[2]',
'tp3-col-end-[3]',
'tp3-col-end-[4]',
'tp3-col-end-[5]',
// ...etc
],
}
there's also a way to use regex, which might look something like this:
// tailwind.config.js
module.exports = {
// ...other stuff
safelist: [
{
pattern: /tp3-col-start-[(0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15)]/,
variants: ['sm', 'lg'], // you can add variants too
},
{
pattern: /tp3-col-end-[(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16)]/,
},
],
}
you can read more about safelisting here safelisting-classes

Hide Docs tab in Storybook

I want to create stories using both Typescript and MDX, therefore I have in my main.js:
module.exports = {
stories: ['../src/**/*.stories.(mdx|ts)'],
addons: ['#storybook/addon-docs', 'storybook-addon-preview']
};
However I don't want to have "Docs" tab next to "Canvas". How do I remove it? Without '#storybook/addon-docs' MDX story is not displayed.
Put this in preview.js:
export const parameters = {
previewTabs: {
'storybook/docs/panel': {
hidden: true
}
}
};
Used in Storybook version 6.0.x
I am currently using #storybook/angular#6.0.21 and the previous answer unfortunately did not work for me. I was able to find a solution in the storybook DocsPage documentation.
The relevant section:
You can replace DocsPage at any level by overriding the docs.page parameter:
- With null to remove docs
- With MDX docs
- With a custom React component
I was able to completely remove the DocsPage for a single story like this:
export const myStory = () => ({
moduleMetadata: MODULE_METADATA,
component: MyComponent,
});
myStory.parameters = {
docs: { page: null },
};

Change css variables dynamically in angular

In my angular project, I have some css variables defined in top level styles.scss file like this. I use these variable at many places to keep the whole theme consistent.
:root {
--theme-color-1: #f7f7f7;
--theme-color-2: #ec4d3b;
--theme-color-3: #ffc107;
--theme-color-4: #686250;
--font-weight: 300
}
How can I update values of these variables dynamically from app.component.ts ? And What is the clean way to do this in angular ?
You can update them using
document.documentElement.style.setProperty('--theme-color-1', '#fff');
If u want to update many values, then create a object
this.styles = [
{ name: 'primary-dark-5', value: "#111" },
{ name: 'primary-dark-7_5', value: "#fff" },
];
this.styles.forEach(data => {
document.documentElement.style.setProperty(`--${data.name}`, data.value);
});
The main thing here is document.documentElement.style.setProperty. This line allows you to access the root element (HTML tag) and assigns/overrides the style values.
Note that the names of the variables should match at both places(css and js files)
if you don't want to use document API, then you can use inline styles on HTML tag directly
const styleObject = {};
this.styles.forEach(data => {
styleObject[`--${data.name}`] = data.value;
});
Then In your template file using ngStyle (https://angular.io/api/common/NgStyle)
Set a collection of style values using an expression that returns
key-value pairs.
<some-element [ngStyle]="objExp">...</some-element>
<html [ngStyle]="styleObject" >...</html> //not sure about quotes syntax
Above methods do the same thing, "Update root element values" but in a different way.
When you used :root, the styles automatically got attached to HTML tag
Starting with Angular v9 you can use the style binding to change a value of a custom property
<app-component-name [style.--theme-color-1="'#CCC'"></app-component-name>
Some examples add variables directly to html tag and it seem in the element source as a long list. I hope this helps to you,
class AppComponent {
private variables=['--my-var: 123;', '--my-second-var: 345;'];
private addAsLink(): void {
const cssVariables = `:root{ ${this.variables.join('')}};
const blob = new Blob([cssVariables]);
const url = URL.createObjectURL(blob);
const cssElement = document.createElement('link');
cssElement.setAttribute('rel', 'stylesheet');
cssElement.setAttribute('type', 'text/css');
cssElement.setAttribute('href', url);
document.head.appendChild(cssElement);
}
}

Resources