Dependency injection in separated files of vue - vue.js

I'm using Vue for my project.
Everything seems to be fine at beginning because I have knowledge about Angular before.
Now, I'm trying to implement DI in my Vue application.
I created a file named user.service.vue :
<script>
export default {
name: '$user',
provide: {
getUser: function(){
console.log('Hello world');
console.log(this.$http);
}
}
}
</script>
And I'm trying to inject the $user into my App.vue as below:
<script>
import NavigationBar from './components/shared/navigation-bar';
import UserService from './services/user.service';
export default {
components: {
NavigationBar
},
inject: ['$user'],
mounted(){
console.log(this.$user);
}
}
</script>
When I run the application, one error message shows up:
vue.runtime.esm.js?2b0e:587 [Vue warn]: Injection "$user" not found
How can I inject separated service files into my Vue app ?
Thanks,
Update 1
Even having changed my user.service.vue to :
<script>
export default {
provide: {
'$user': 'Hello world'
}
}
</script>
I still have the same problem.
But when I changed my App.vue to :
export default {
components: {
NavigationBar
},
provide: {
'$user': 'From app'
}
}
And create one component which is App's child: (Call it user-dashboard.vue for example)
export default {
name: 'user-dashboard',
inject: ['$user'],
data() {
return {
users: []
}
},
mounted() {
console.log(this.$user);
}
}
When I run the app, I get the message: From app in my console.
What I'm thinking is: the $user service must be provided by App.vue to inject to its children.
But in my application, there could me many services, such as: user.service, customer.service, ... Therefore, I cannot declare everything in my App.vue. It will be hard to maintain and fix bugs.
Any solutions please ?

What's wrong with using the service directly in your script?
see below:
<script>
import NavigationBar from './components/shared/navigation-bar';
import UserService from './services/user.service';
export default {
components: {
NavigationBar
},
mounted(){
UserService.someMethod().then((response) => {
console.log(response);
})
}
}
</script>

Related

Can't mock module import in Cypress Vue component tests

I'm new to Cypress component testing, but I was able to set it up easily for my Vue project. I'm currently investigating if we should replace Jest with Cypress to test our Vue components and I love it so far, there is only one major feature I'm still missing: mocking modules. I first tried with cy.stub(), but it simply didn't work which could make sense since I'm not trying to mock the actual module in Node.js, but the module imported within Webpack.
To solve the issue, I tried to use rewiremock which is able to mock Webpack modules, but I'm getting an error when running the test:
I forked the examples from Cypress and set up Rewiremock in this commit. Not sure what I'm doing wrong to be honest.
I really need to find a way to solve it otherwise, we would simply stop considering Cypress and just stick to Jest. If using Rewiremock is not the way, how am I suppose to achieve this? Any help would be greatly appreciated.
If you are able to adjust the Vue component to make it more testable, the function can be mocked as a component property.
Webpack
When vue-loader processes HelloWorld.vue, it evaluates getColorOfFruits() and sets the data property, so to mock the function here, you need a webpack re-writer like rewiremock.
export default {
name: 'HelloWorld',
props: {
msg: String
},
data() {
return {
colorOfFruits: getColorOfFruits(), // during compile time
};
},
...
Vue created hook
If you initiialize colorOfFruits in the created() hook, you can stub the getColorOfFruits function after import but prior to mounting.
HelloWorld.vue
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h2>{{ colorOfFruits.apple }}</h2>
<p>
...
</template>
<script lang="ts">
import { getColorOfFruits } from "#/helpers/fruit.js";
export default {
name: "HelloWorld",
getColorOfFruits, // add this function to the component for mocking
props: {
msg: String,
},
data() {
return {
colorOfFruits: {} // initialized empty here
};
},
created() {
this.colorOfFruits = this.$options.colorOfFruits; // reference function saved above
}
});
</script>
HelloWorld.spec.js
import { mount } from "#cypress/vue";
import HelloWorld from "./HelloWorld.vue";
it("mocks an apple", () => {
const getMockFruits = () => {
return {
apple: "green",
orange: "purple",
}
}
HelloWorld.getColorOfFruits = getMockFruits;
mount(HelloWorld, { // created() hook called as part of mount()
propsData: {
msg: "Hello Cypress!",
},
});
cy.get("h1").contains("Hello Cypress!");
cy.get("h2").contains('green')
});
We solved this by using webpack aliases in the Cypress webpack config to intercept the node_module dependency.
So something like...
// cypress/plugins/index.js
const path = require("path");
const { startDevServer } = require('#cypress/webpack-dev-server');
// your project's base webpack config
const webpackConfig = require('#vue/cli-service/webpack.config');
module.exports = (on, config) => {
on("dev-server:start", (options) => {
webpackConfig.resolve.alias["your-module"] = path.resolve(__dirname, "path/to/your-module-mock.js");
return startDevServer({
options,
webpackConfig,
})
});
}
// path/to/your-module-mock.js
let yourMock;
export function setupMockModule(...) {
yourMock = {
...
};
}
export default yourMock;
// your-test.spec.js
import { setupMock } from ".../your-module-mock.js"
describe("...", () => {
before(() => {
setupMock(...);
});
it("...", () => {
...
});
});

Convert Vue2 component (factory) to Vue3 with render function

I would like to convert an existing Vue2 component to Vue3 but I'm having problems. Basically what you see below is what I come up so far (with irrelevant code left out). I could share the Vue2 code as well but it does not look very different.
When running the app I get the error Component is missing template or render function. So what could be the issue here? Is it because there is possibly a different instance of Vue being defined with createApp?
I think the inner workings of the mounted and other hooks are not that important right now. What is puzzling me is that the render function is not even called. I checked that with a log statement.
import { createApp, h } from "vue";
const app = createApp({});
export default (Component, tag = "span") =>
app.component("adaptor", {
render() {
return h(tag, {
ref: "container",
props: this.$attrs,
});
},
data() {
return {
comp: null,
};
},
mounted() {
this.comp = new Component({
target: this.$refs.container,
props: this.$attrs,
});
},
updated() {
this.comp.$set(this.$attrs);
},
unmounted() {
this.comp.$destroy();
},
});
It basically provides a wrapper to render components of other frameworks in Vue. It is used like this in Vue2 at the moment:
import toVue from "../toVue";
[...]
{ components: { MyComp: toVue(MyOtherFrameworkComp) }
[...]

vue.js 2 single file component with dynamic template

I need a single file component to load its template via AJAX.
I search a while for a solution and found some hints about dynamic components.
I crafted a combination of a parent component which imports a child component and renders the child with a dynamic template.
Child component is this:
<template>
<div>placeholder</div>
</template>
<script>
import SomeOtherComponent from './some-other-component.vue';
export default {
name: 'child-component',
components: {
'some-other-component': SomeOtherComponent,
},
};
</script>
Parent component is this
<template>
<component v-if='componentTemplate' :is="dynamicComponent && {template: componentTemplate}"></component>
</template>
<script>
import Axios from 'axios';
import ChildComponent from './child-component.vue';
export default {
name: 'parent-component',
components: {
'child-component': ChildComponent,
},
data() {
return {
dynamicComponent: 'child-component',
componentTemplate: null,
};
},
created() {
const self = this;
this.fetchTemplate().done((htmlCode) => {
self.componentTemplate = htmlCode;
}).fail((error) => {
self.componentTemplate = '<div>error</div>';
});
},
methods: {
fetchTemplate() {
const formLoaded = $.Deferred();
const url = '/get-dynamic-template';
Axios.get(url).then((response) => {
formLoaded.resolve(response.data);
}).catch((error) => {
formLoaded.reject(error);
}).then(() => {
formLoaded.reject();
});
return formLoaded;
},
},
};
</script>
The dynamic template code fetched is this:
<div>
<h1>My dynamic template</h1>
<some-other-component></some-other-component>
</div>
In general the component gets its template as expected and binds to it.
But when there are other components used in this dynamic template (some-other-component) they are not recognized, even if they are correctly registered inside the child component and of course correctly named as 'some-other-component'.
I get this error: [Vue warn]: Unknown custom element: some-other-component - did you register the component correctly? For recursive components, make sure to provide the "name" option.
Do I miss something or is it some kind of issue/bug?
I answer my question myself, because I found an alternative solution after reading a little bit further here https://forum.vuejs.org/t/load-html-code-that-uses-some-vue-js-code-in-it-via-ajax-request/25006/3.
The problem in my code seems to be this logical expression :is="dynamicComponent && {template: componentTemplate}". I found this approach somewhere in the internet.
The original poster propably assumed that this causes the component "dynamicComponent" to be merged with {template: componentTemplate} which should override the template option only, leaving other component options as defined in the imported child-component.vue.
But it seems not to work as expected since && is a boolean operator and not a "object merge" operator. Please somebody prove me wrong, I am not a JavaScript expert after all.
Anyway the following approach works fine:
<template>
<component v-if='componentTemplate' :is="childComponent"></component>
</template>
<script>
import Axios from 'axios';
import SomeOtherComponent from "./some-other-component.vue";
export default {
name: 'parent-component',
components: {
'some-other-component': SomeOtherComponent,
},
data() {
return {
componentTemplate: null,
};
},
computed: {
childComponent() {
return {
template: this.componentTemplate,
components: this.$options.components,
};
},
},
created() {
const self = this;
this.fetchTemplate().done((htmlCode) => {
self.componentTemplate = htmlCode;
}).fail((error) => {
self.componentTemplate = '<div>error</div>';
});
},
methods: {
fetchTemplate() {
const formLoaded = $.Deferred();
const url = '/get-dynamic-template';
Axios.get(url).then((response) => {
formLoaded.resolve(response.data);
}).catch((error) => {
formLoaded.reject(error);
}).then(() => {
formLoaded.reject();
});
return formLoaded;
},
},
};
</script>

Call a method of another component

How to call a method of another component ?
Like I have a component named Modal.vue . I have a method like below
<script>
export default {
name: 'modal'
methods: {
getUsers() {
//some code here
}
},
created: function () {
this.getUsers();
}
}
</script>
I would like to call that method in another component named Dashboard.vue.
<script>
export default {
name: 'dashboard'
methods: {
add_adddress () {
this.getUsers(); // I would like to access here like this
//some code here
}
},
}
</script>
I read this question, but how can I use $emit,$on,$broadcast in my current setup ?
In order to use emit one of the components need to call the other (parent and child). Then you emit from the child component to the parent component. For example if Dashboard component uses the Modal component you can emit from the Modal to the Dashboad.
If your components are separate from one another you can make use of Mixins. You can create a mixin UsersMixin.js like the following:
export default {
methods: {
getUsers: function () {
// Put your code here for a common method
}
}
}
Then import the mixin in both your components, and you will be able to use its methods:
Modal.vue
<script>
import UsersMixin from './UsersMixin';
export default {
name: 'modal'
mixins: [
UsersMixin
],
created: function () {
this.getUsers();
}
}
</script>
Dashboard.vue
<script>
import UsersMixin from './UsersMixin';
export default {
name: 'dashboard',
mixins: [
UsersMixin
],
methods: {
add_adddress () {
this.getUsers(); // I would like to access here like this
//some code here
}
},
}
</script>

AngularJS services in Vue.js

I'm new to Vue.js and looking for the equivalent of a service in AngularJS, specifically for storing data once and getting it throughout the app.
I'll be mainly storing the results of network requests and other promised data so I don't need to fetch again on very state.
I'm using Vue.JS 2.0 with Webpack.
Thanks!
I think what u are seeking for is vuex, which can share data from each component.
Here is a basic demo which from my code.
store/lottery.module.js
import lotteryType from './lottery.type'
const lotteryModule = {
state: {participantList: []},
getters: {},
mutations: {
[lotteryType.PARTICIPANT_CREATE] (state, payload) {
state.participantList = payload;
}
},
actions: {
[lotteryType.PARTICIPANT_CREATE] ({commit}, payload) {
commit(lotteryType.PARTICIPANT_CREATE, payload);
}
}
};
export default lotteryModule;
store/lottery.type.js
const PARTICIPANT_CREATE = 'PARTICIPANT_CREATE';
export default {PARTICIPANT_CREATE};
store/index.js
Vue.use(Vuex);
const store = new Vuex.Store();
store.registerModule('lottery', lotteryModule);
export default store;
component/lottery.vue
<template>
<div id="preparation-container">
Total Participants: {{participantList.length}}
</div>
</template>
<script>
import router from '../router';
import lotteryType from '../store/lottery.type';
export default {
data () {
return {
}
},
methods: {
},
computed: {
participantList() {
return this.$store.state.lottery.participantList;
}
},
created() {
this.$store.dispatch(lotteryType.PARTICIPANT_CREATE, [{name:'Jack'}, {name:'Hugh'}]);
},
mounted() {
},
destroyed() {
}
}
</script>
You don't need Vue-specific services in Vue2 as it is based on a modern version of JavaScript that uses Modules instead.
So if you want to reuse some services in different locations in your code, you could define and export it as follows:
export default {
someFunction() {
// ...
},
someOtherFunction() {
// ...
}
};
And then import from your Vue code:
import service from 'filenameofyourresources';
export default {
name: 'something',
component: [],
data: () => ({}),
created() {
service.someFunction();
},
};
Note that this is ES6 code that needs to be transpiled to ES5 before you can actually use it todays browsers.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export