How to pass props to all routes in vue.js? - javascript

I want to pass BASE_URL to all components. My App.js is like:
<template>
<router-view></router-view>
</template>
<script>
import addJoke from './components/addJoke.vue'
import showJokesAll from './components/showJokesAll.vue'
export default {
components: {
'add-joke': addJoke,
'show-jokes-all': showJokesAll
},
data () {
return {
BASE_URL : 'http://127.0.0.1:8090'
}
}
}
</script>
<style>
</style>
And the routes.js:
import showJokesAll from './components/showJokesAll.vue';
import addJoke from './components/addJoke.vue';
export default [
{path:'/', component: showJokesAll, props: {BASE_URL: 'http://127.0.0.1:8090'} },
{path:'/add', component: addJoke, props: {BASE_URL: 'http://127.0.0.1:8090'} }
]
and in showJokesAll component I have:
<script>
import axios from 'axios';
export default {
name: 'showJokesAll',
props: ['BASE_URL'],
data () {
return {
jokes:[]
}
},
methods: {
},
created() {
axios.get( BASE_URL + '/api/jokes').then( response => this.jokes = response.data);
}
}
</script>
But the components is not received BASE_URL.
[Vue warn]: Error in created hook: "ReferenceError: BASE_URL is not
defined"
How can I fix this?

To access the prop define with props: ['BASE_URL'], you would use this.BASE_URL:
axios.get( this.BASE_URL + '/api/jokes').then(/*...*/)

You can write a Mixins file which contains data or a function which returns your BASE_URL and then import mixins file using,
import myMixins from './my_mixins.js'
Mixins are a flexible way to distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixin will be “mixed” into the component’s own options.
If you want to manage the state of the data you should have a look at Vuex.
Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.
Updated:
Also have a look a Vue Instance Properties.

Related

How to throw data to main App.vue from views? [duplicate]

I have two components:
App.vue
Sidekick.vue
In my App.vue component, I have a property that I would like to access from Sidekick.vue
App.vue
<template>
<div id="app">
<p>{{ myData }}</p>
<div class="sidebar">
<router-view/> // our sidekick component is shown here
</div>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
myData: 'is just this string'
}
}
}
</script>
Sidekick.vue
<template>
<div class="sidekick">
{{ myData }}
</div>
</template>
<script>
export default {
name: 'Sidekick'
}
</script>
I would like access to myData (which is declared in App.vue) from Sidekick.vue
I have tried importing App.vue from within Sidekick.vue by doing something like:
Sidekick.vue (incorrect attempt)
<script>
import App from '#/App'
export default {
name: 'Sidekick',
data () {
return {
myData: App.myData
}
}
}
</script>
I have read about props - but have only seen references to child / parent components. In my case, Sidekick.vue is shown in a div inside App.vue (not sure if this makes it a "child"). Do I need to give access of myData to <router-view/> somehow?
UPDATE: (to show relationship between App.vue and Sidekick.vue
index.js (router file)
import Vue from 'vue'
import Router from 'vue-router'
import Sidekick from '#/components/Sidekick',
import FakeComponent from '#/components/FakeComponent'
Vue.use(Router)
const router = new Router({
routes: [
{
path: '/',
redirect: '/fakecomponent'
},
{
path: '/sidekick',
name: 'Sidekick',
component: Sidekick
},
{
path: '/fakecomponent',
name: 'FakeComponent',
component: FakeComponent
}
]
})
export default router
Sidekick.vue gets rendered when we hit /sidekick
Just keep in mind, the rule of thumb is using props to pass data in a one-way flow
props down, events up.
https://v2.vuejs.org/v2/guide/components.html#Composing-Components
Quick solution:
Global event bus to post messages between your <App/> and <Sidekick/> components.
https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
Long term solution:
Use a state management library like vuex to better encapsulates data in one place (a global store) and subscribe it from your components tree using import { mapState, mapMutations } from 'vuex'
When you have parent-child communication, the best and recommended
option is to use props and events. Read more in Vue docs
When want to have shared state between many components the best and
recommended way is to use Vuex.
If you want to use simple data sharing you can use Vue observable.
Simple example: Say that you have a game and you want the errors to be accessible by many components. (components can access it and manipulate it).
errors.js
import Vue from "vue";
export const errors = Vue.observable({ count: 0 });
Component1.vue
import { errors } from 'path-of-errors.js'
export default {
computed: {
errors () {
get () { return errors.count },
set (val) { errors.count = val }
}
}
}
In Component1 the errors.count is reactive. So if as a template you have:
<template>
<div>
Errors: {{ errors }}
<button #click="errors++">Increase</button>
</div>
</template>
While you click the Increase button, you will see the errors increasing.
As you might expect, when you import the errors.js in another component, then both components can participate on manipulating the errors.count.
Note: Even though you might use the Vue.observable API for simple data sharing you should be aware that this is a very powerful API. For example read Using Vue Observables as a State Store
App.vue:
<router-view pass_data='myData'/>
Sidekick.vue:
export default {
name: "Sidekick",
props: ["pass_data"],
created() {
alert("pass_data: "+this.pass_data)
}
}
If App.js(Parent) and Sidekick(Child)
App.js
in Template
In script
import Sidekick from './Sidekick.vue:
Sidekick.vue
props: ['myData']
now you can access myData anywhere in sidekick.
In template myData and
in scripts this.myData

Vue JS typescript component cannot find inject instance properties

I'm working with typescript and vue.
In my app there is a service which is a global for every sub component.
I found this native vue solution on vue JS to inject this property on the child components.
on main.ts
const service = new MyService(...);
new Vue({
router,
provide() { return { service }; } // provide the service to be injected
render: h => h(App),
}).$mount("#app");
on any typescript vue component
import Vue from "vue";
export default Vue.extend({
inject: ["service"], // inject the service
mounted() {
console.log(this.service); // this.service does exists
},
});
This way I'm able to get the injected service on my Child components.
However I'm getting the fallowing error
Property 'service' does not exist on type 'CombinedVueInstance < Vue, {}, {}, {}, Readonly < Record < never, any > > >'.
How can I solve this typescript compilation error?
You don't need to use class components in order to get this. For object component declaration you have two ways of doing it:
Patching data return type
export default {
inject: ['myInjection'],
data(): { myInjection?: MyInjection } {
return {}
},
}
The downside is that you'll have to mark it as optional to be able to add it do data return.
Extending Vue context
declare module 'vue/types/vue' {
interface Vue {
myInjection: MyInjection
}
}
export default {
inject: ['myInjection'],
}
Using plugins
If you whant to use some service in all Vue components, you can try to use plugins.
In main.ts you just import it:
import Vue from "vue";
import "#/plugins/myService";
In plugins/myService.ts you must write someting like this:
import _Vue from "vue";
import MyService from "#/services/MyService";
export default function MyServicePlugin(Vue: typeof _Vue, options?: any): void {
Vue.prototype.$myService = new MyService(...); // you can import 'service' from other place
}
_Vue.use(MyServicePlugin);
And in any vue component you can use this:
<template>
<div> {{ $myService.name }}</div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
#Component
export default class MyComponent extends Vue {
private created() {
const some = this.$myService.getStuff();
}
}
</script>
Don't forget to declare $myService on d.ts file. Somewhere in your project add file myService.d.ts with the following content:
import MyService from "#/services/MyService";
declare module "vue/types/vue" {
interface Vue {
$myService: MyService;
}
}
 Using Vue property decorator
Vue-property-decorator, which internally re-exports decorators from vue-class-component, expose a series of typescript decorators that give really good intellisense. You must use the class api though.
#Inject and #Provide are two of such decorators:
In the provider:
import {Vue, Component, Provide} from 'vue-property-decorator';
#Component
export default class MyClass {
#Provide('service') service: Service = new MyServiceImpl(); // or whatever this is
}
In the provided component:
import {Vue, Component, Inject} from 'vue-property-decorator';
#Component
export default class MyClass {
#inject('service') service!: Service; // or whatever type this service has
mounted() {
console.log(this.service); // no typescript error here
},
}
This I think is the optimal solution, in the sense it gives the better intellisense available when working with Vue.
Now, however, you may don't want to change the definition of all your components or simply cannot due to external constraints. In such case you can do the next trick:
Casting this
You can cast this to any whenever you're about to use this.service. Not probably the best thing, but it works:
mounted() {
console.log((this as any).service);
},
There must be other ways, but I'm not used to Vue.extends api anymore. If you have the time and the opportunity, I strongly suggest you to switch to the class API and start using the vue-property-decorators, they really give the best intellisense.
Extend Vue for injected components only
Create an interface for the injection and extend Vue for the specific components where it is needed:
main.ts:
const service = new MyService(...);
export interface ServiceInjection {
service: MyService;
}
new Vue({
router,
provide(): ServiceInjection { return { service }; } // provide the service to be injected
render: h => h(App),
}).$mount("#app");
A components that uses your interface
import Vue from "vue";
import { ServiceInjection } from 'main.ts';
export default ( Vue as VueConstructor<Vue & ServiceInjection> ).extend({
inject: ["service"], // inject the service
mounted() {
console.log(this.service); // this.service does exists
},
});

How do I unit test a Vue.js component that relies on a complicated Vuex store and extends another component?

I am wondering how one goes about unit testing a component that uses a complicated Vuex store and extends another component.
Can someone please provide me with an example of how I might go about creating a test that simply asserts that a component that extends another component and relies on Vuex mounts and displays some simple text?
I've tried using vue-test-utils to shallowMount the component under test, but I can't get my test to fail because it has issues even building and mounting the component. As far as I can tell, this is a result of the component leveraging the extended component, and because both components rely on a complicated Vuex store.
Any kind of examples would be appreciated. Thanks.
EDIT:
For further context, our store is broken up into modules. Here is what the store definition file looks like:
/* global phpvars */
import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import * as mutations from './mutations'
import acloverrides from '../../modules/acloverrides'
import api from '../../modules/api'
import callback from '../../modules/callback'
import clearlink from '../../modules/clearlink'
import configuration from '../../modules/configuration'
import customer from '../../modules/customer'
import drKeepAlive from '../../modules/drkeepalive'
import interaction from './modules/interaction'
import ivr from './modules/ivr'
import marketing from '../../modules/marketing'
import opportunities from './modules/opportunities'
import order from '../../modules/order'
import orderNotes from './modules/notes'
import products from '../../modules/products'
import sidebar from './modules/sidebar'
import sparks from './modules/sparks'
import training from '../../modules/training'
import transformers from '../../modules/transformers'
import user from '../../modules/user'
let brand = require('../brands/' + phpvars.brand.name + '/modules/brand').default
let forms = require('../brands/' + phpvars.brand.name + '/modules/forms').default
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
acloverrides,
api,
brand,
callback,
clearlink,
configuration,
customer,
drKeepAlive,
forms,
interaction,
ivr,
marketing,
opportunities,
order,
orderNotes,
products,
sidebar,
sparks,
training,
transformers,
user
},
state: {
availability: {
status: false,
results: {}
},
navigation: {
enabled: phpvars.user.access.order.navigate,
restrictTo: []
},
routes: [],
router: {},
editMode: false // Used to determine if the user has clicked the edit button on an existing order.
},
actions,
getters,
mutations
})
And here is my unit test file:
import Vuex from 'vuex'
import { shallowMount, createLocalVue } from '#vue/test-utils'
import SelectedProducts from '../../resources/assets/js/components/formfields/products/SelectedProducts'
import BaseField from '../../resources/assets/js/components/BaseField'
import store from '../../resources/assets/js/orderform/store/index.js'
const Vue = createLocalVue()
Vue.use(Vuex)
describe('SelectedProducts', () => {
fit('sanity check', () => {
window.phpvars = {
brand: {
name: 'foobar'
},
user: {
access: {
order: {
navigate: true
}
}
}
}
const wrapper = shallowMount(SelectedProducts, {
store: new Vuex.Store(store)
})
expect(wrapper.text()).toContain('Selected Products')
})
})
I find Vue documentation on unit testing to be a bit vague. Give this a shot:
import {createLocalVue, shallowMount} from '#vue/test-utils';
import Vuex from 'vuex';
import store from 'path/to/store/index.js';
import Component from 'path/to/Component';
// create a local instance that uses
// the store, should follow a pattern present
// in your src/main.js
const localVue = createLocalVue();
localVue.use(Vuex);
describe(
'Component', () => {
test('renders', () => {
const wrapper = shallowMount(Component, {
store: new Vuex.Store(store)
});
expect(wrapper.isVueInstance()).toStrictEqual(true);
});
}
);
EDIT for your edit
In Jest, window is replaced by global. So you could mock your phpvars with:
global.phpvars = {
brand: {
name: 'foobar'
},
user: {
access: {
order: {
navigate: true
}
}
}
};
You'll want to place that before you import your store.
Components that extend other components shouldn't be tested any differently, they essentially compile down to a single component in terms of variables and requirements. If you could expand on that question, I'd be happy to answer (like, what issues or errors you are encountering unique to your extended components).
I haven't tested anything I've mentioned so if you do continue to have errors I'll throw together a project. Good luck!

Export custom javascript file to a Vue component

I am a beginner in Vue.js and so this question might be duplicate or naive. I want to call functions defined in a custom javascript file within a Vue component. I did something like this.
custom.js
class API{
function testCall(){
alert("test ok");
}
}
export {API}
App.vue
<template>
<div id="app">
<img src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<testcomponent :on-click="getData">
</testcomponent>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue';
import TestComponent from './components/TestComponent.vue';
import API from './js/custom.js';
export default {
name: 'app',
components: {
HelloWorld,
TestComponent,
API
},
methods: {
getData(){
const apiObj = new API();
apiObj.testCall();
}
}
}
</script>
When I build using npm run build, I get below error.
Any help with this please?
1: To define methods in a class you do not need function keyword.
class API{
testCall(){
alert("test ok");
}
}
2: Since you are doing a named export using export {API}, your import statement should be
import {API} from './js/custom.js';
3:components options is for registering vue components locally. Since API is not a vue component remove it from the components option.
API is not a Vue component - you should not include it inside the components branch. Also, if this is just a bunch of utility functions you can either export them one by one or as a containing object
// util.js - individual functions
export function testCall (call) {};
export function testUser (user) {};
// Vue app
import { testCall, testUser } from 'util.js';
// util.js - object group
function testCall (call)
{
}
function testUser (user)
{
}
export default
{
testCall,
testUser
}
// Vue app
import API from 'util.js';

Vuejs Share Data Between Components

I have two components:
App.vue
Sidekick.vue
In my App.vue component, I have a property that I would like to access from Sidekick.vue
App.vue
<template>
<div id="app">
<p>{{ myData }}</p>
<div class="sidebar">
<router-view/> // our sidekick component is shown here
</div>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
myData: 'is just this string'
}
}
}
</script>
Sidekick.vue
<template>
<div class="sidekick">
{{ myData }}
</div>
</template>
<script>
export default {
name: 'Sidekick'
}
</script>
I would like access to myData (which is declared in App.vue) from Sidekick.vue
I have tried importing App.vue from within Sidekick.vue by doing something like:
Sidekick.vue (incorrect attempt)
<script>
import App from '#/App'
export default {
name: 'Sidekick',
data () {
return {
myData: App.myData
}
}
}
</script>
I have read about props - but have only seen references to child / parent components. In my case, Sidekick.vue is shown in a div inside App.vue (not sure if this makes it a "child"). Do I need to give access of myData to <router-view/> somehow?
UPDATE: (to show relationship between App.vue and Sidekick.vue
index.js (router file)
import Vue from 'vue'
import Router from 'vue-router'
import Sidekick from '#/components/Sidekick',
import FakeComponent from '#/components/FakeComponent'
Vue.use(Router)
const router = new Router({
routes: [
{
path: '/',
redirect: '/fakecomponent'
},
{
path: '/sidekick',
name: 'Sidekick',
component: Sidekick
},
{
path: '/fakecomponent',
name: 'FakeComponent',
component: FakeComponent
}
]
})
export default router
Sidekick.vue gets rendered when we hit /sidekick
Just keep in mind, the rule of thumb is using props to pass data in a one-way flow
props down, events up.
https://v2.vuejs.org/v2/guide/components.html#Composing-Components
Quick solution:
Global event bus to post messages between your <App/> and <Sidekick/> components.
https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
Long term solution:
Use a state management library like vuex to better encapsulates data in one place (a global store) and subscribe it from your components tree using import { mapState, mapMutations } from 'vuex'
When you have parent-child communication, the best and recommended
option is to use props and events. Read more in Vue docs
When want to have shared state between many components the best and
recommended way is to use Vuex.
If you want to use simple data sharing you can use Vue observable.
Simple example: Say that you have a game and you want the errors to be accessible by many components. (components can access it and manipulate it).
errors.js
import Vue from "vue";
export const errors = Vue.observable({ count: 0 });
Component1.vue
import { errors } from 'path-of-errors.js'
export default {
computed: {
errors () {
get () { return errors.count },
set (val) { errors.count = val }
}
}
}
In Component1 the errors.count is reactive. So if as a template you have:
<template>
<div>
Errors: {{ errors }}
<button #click="errors++">Increase</button>
</div>
</template>
While you click the Increase button, you will see the errors increasing.
As you might expect, when you import the errors.js in another component, then both components can participate on manipulating the errors.count.
Note: Even though you might use the Vue.observable API for simple data sharing you should be aware that this is a very powerful API. For example read Using Vue Observables as a State Store
App.vue:
<router-view pass_data='myData'/>
Sidekick.vue:
export default {
name: "Sidekick",
props: ["pass_data"],
created() {
alert("pass_data: "+this.pass_data)
}
}
If App.js(Parent) and Sidekick(Child)
App.js
in Template
In script
import Sidekick from './Sidekick.vue:
Sidekick.vue
props: ['myData']
now you can access myData anywhere in sidekick.
In template myData and
in scripts this.myData

Categories

Resources