AngularJS services in Vue.js - 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

Related

How can I pass the arguments to the NUXT component in Storybook?

I've got a NUXT/VUE component which does an API call and assigns the result to the data, therefore template maps the result to the page. Simple API call.
I am just not sure how I can do that in Storybook component?
Do I have to mock the API fetch or pass the static data to the component in the Storybook?
Examples on the the official website is all about props, nothing about data
https://storybook.js.org/docs/react/writing-stories/args
Here is my simple component
<template>
<div>{{blogPosts.title}}</div>
</template>
<script>
export default {
data() {
return {
blogPosts: [],
};
},
async fetch() {
this.blogPosts = await this.$http.$get("https://api.nuxtjs.dev/posts");
},
};
</script>
Here is my Storybook Component:
import { Meta, Story } from "#storybook/vue";
import BlogCarousel from "./BlogCarousel.vue";
import { BlogPost } from "~/lib/types/BlogPost";
export default {
title: "BlogCarousel",
components: BlogCarousel,
} as Meta;
const Template: Story<BlogPost> = (args) => {
return {
components: { BlogCarousel },
template: `<BlogCarousel v-bind=${args.blogPosts} />`,
};
};
export const Default = Template.bind({});
Default.args = {
blogPosts: [
{
title: "test",
created: "today",
},
],
};

Vue JS. Layered calls not synchronised: Web Page -> VUEX-> API call

I have refactored my VUE JS code to have a dedicated API layer (Calls out to AWS Graphql services), it is called by the VUEX layer. It now has the following levels:
Web Page -> Vuex -> API
I want to retrieve data (this.getActivities) before referencing it (Point 7). I have cut down the code for simplicity:
async created() {
console.log("Point 1")
await this.getActivities();
},
mounted() {
console.log("Point 7")
// reference the data set by this.getActivities()
},
methods: {
async getActivities() {
// from DB
console.log("Point 2")
this.$store.dispatch('getAllActivities') // vuex call
},
VUEX DATA STORE
actions: {
async getAllActivities ({ commit }) {
console.log("point 3")
const activities = await queries.getActivities()
console.log("point 6")
commit('setActivities', activities)
},
API
async getActivities () {
await API.graphql({
query: listActivities
}).then((response) => {
console.log("Point 4")
})
console.log("Point 5")
return activitiesList
},
Prints the following:
Point 1
Point 2
point 3
Point 7
Point 8
Point 4
Point 5
point 6
I presume I have misused the await/sync processes?
Thanks
Assuming that you need the list of activities in more than 1 component/route (otherwise why would you store this list in Vuex instead of the component itself ?!) you would normally do something like this:
<template>
<div>
<ActivityItem v-for="act in ACTIVITY_LIST" :key="act.id" :activity="act" />
</div>
</template>
<script>
import ActivityItem from './components/ActivityItem';
import { mapGetters, mapActions } from 'vuex';
import { ACTIVITY_LIST, FETCH_ACTIVITIES } from './store/constants';
export default
{
components:
{
ActivityItem,
},
computed:
{
...mapGetters([ACTIVITY_LIST]),
},
created()
{
this[FECH_ACTIVITIES]();
},
methods:
{
...mapActions([FETCH_ACTIVITIES])
}
}
</script>
// store/constants.js
export const ACTIVITY_LIST = 'ACTIVITY_LIST';
export const FETCH_ACTIVITIES = 'FETCH_ACTIVITIES';
export const SET_ACTIVITIES = 'SET_ACTIVITIES';
// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import { ACTIVITY_LIST, FETCH_ACTIVITIES, SET_ACTIVITIES } from './store/constants';
import myAPI from './api';
Vue.use(Vuex);
export default new Vuex.Store(
{
strict: process.env.NODE_ENV !== 'production',
state()
{
return {
activities: []
};
},
getters:
{
[ACTIVITY_LIST](state)
{
return state.activities;
}
},
mutations:
{
[SET_ACTIVITIES](state, value)
{
state.activities = value || [];
}
},
actions:
{
[FETCH_ACTIVITIES]({ commit })
{
return myAPI.getActivities().then(response =>
{
commit(SET_ACTIVITIES, response.data.activitiesList);
return response.data.activitiesList; // optional
});
}
}
});

Vue3 reactive components on globalProperties

In vuejs 2 it's possible to assign components to global variables on the main app instance like this...
const app = new Vue({});
Vue.use({
install(Vue) {
Vue.prototype.$counter = new Vue({
data: () => ({ value: 1 }),
methods: {
increment() { this.value++ },
}
});
}
})
app.$mount('#app');
But when I convert that to vue3 I can't access any of the properties or methods...
const app = Vue.createApp({});
app.use({
install(app) {
app.config.globalProperties.$counter = Vue.createApp({
data: () => ({ value: 1 }),
methods: {
increment() { this.value++ }
}
});
}
})
app.mount('#app');
Here is an example for vue2... https://jsfiddle.net/Lg49anzh/
And here is the vue3 version... https://jsfiddle.net/Lathvj29/
So I'm wondering if and how this is still possible in vue3 or do i need to refactor all my plugins?
I tried to keep the example as simple as possible to illustrate the problem but if you need more information just let me know.
Vue.createApp() creates an application instance, which is separate from the root component of the application.
A quick fix is to mount the application instance to get the root component:
import { createApp } from 'vue';
app.config.globalProperties.$counter = createApp({
data: () => ({ value: 1 }),
methods: {
increment() { this.value++ }
}
}).mount(document.createElement('div')); 👈
demo 1
However, a more idiomatic and simpler solution is to use a ref:
import { ref } from 'vue';
const counter = ref(1);
app.config.globalProperties.$counter = {
value: counter,
increment() { counter.value++ }
};
demo 2
Not an exact answer to the question but related. Here is a simple way of sharing global vars between components.
In my main app file I added the variable $navigationProps to global scrope:
let app=createApp(App)
app.config.globalProperties.$navigationProps = {mobileMenuClosed: false, closeIconHidden:false };
app.use(router)
app.mount('#app')
Then in any component where I needed that $navigationProps to work with 2 way binding:
<script>
import { defineComponent, getCurrentInstance } from "vue";
export default defineComponent({
data: () => ({
navigationProps:
getCurrentInstance().appContext.config.globalProperties.$navigationProps,
}),
methods: {
toggleMobileMenu(event) {
this.navigationProps.mobileMenuClosed =
!this.navigationProps.mobileMenuClosed;
},
hideMobileMenu(event) {
this.navigationProps.mobileMenuClosed = true;
},
},
Worked like a charm for me.
The above technique worked for me to make global components (with only one instance in the root component). For example, components like Loaders or Alerts are good examples.
Loader.vue
...
mounted() {
const currentInstance = getCurrentInstance();
if (currentInstance) {
currentInstance.appContext.config.globalProperties.$loader = this;
}
},
...
AlertMessage.vue
...
mounted() {
const currentInstance = getCurrentInstance();
if (currentInstance) {
currentInstance.appContext.config.globalProperties.$alert = this;
}
},
...
So, in the root component of your app, you have to instance your global components, as shown:
App.vue
<template>
<v-app id="allPageView">
<router-view name="allPageView" v-slot="{Component}">
<transition :name="$router.currentRoute.name">
<component :is="Component"/>
</transition>
</router-view>
<alert-message/> //here
<loader/> //here
</v-app>
</template>
<script lang="ts">
import AlertMessage from './components/Utilities/Alerts/AlertMessage.vue';
import Loader from './components/Utilities/Loaders/Loader.vue';
export default {
name: 'App',
components: { AlertMessage, Loader }
};
</script>
Finally, in this way you can your component in whatever other components, for example:
Login.vue
...
async login() {
if (await this.isFormValid(this.$refs.loginObserver as FormContext)) {
this.$loader.activate('Logging in. . .');
Meteor.loginWithPassword(this.user.userOrEmail, this.user.password, (err: Meteor.Error | any) => {
this.$loader.deactivate();
if (err) {
console.error('Error in login: ', err);
if (err.error === '403') {
this.$alert.showAlertFull('mdi-close-circle', 'warning', err.reason,
'', 5000, 'center', 'bottom');
} else {
this.$alert.showAlertFull('mdi-close-circle', 'error', 'Incorrect credentials');
}
this.authError(err.error);
this.error = true;
} else {
this.successLogin();
}
});
...
In this way, you can avoid importing those components in every component.

Why action of Vuex returns a promise<pending>?

I have an action in Vuex actions which commit a mutation that it take a payload from the component, that is a number of the index for returning an object, it works fine on Vuex js file meaning that shows the selected item on the console, as I said it gets index from the payload,
but on the component, it gives me Promise <Pending>, why that's happening? for now, I do not use any API for my Nuxt/Vue app, but I will, and for now, I just want to know why this is happening and what is the best solution for solving this
Here my Vuex codes:
export const state = () => ({
articles: [
{
uid: 0,
img: 'https://raw.githubusercontent.com/muhammederdem/mini-player/master/img/1.jpg',
link: '/articles/1',
},
{
uid: 1,
img: 'https://raw.githubusercontent.com/muhammederdem/mini-player/master/img/2.jpg',
link: '/articles/2',
},
],
})
export const getters = {
getArticles(state) {
return state.articles
},
}
export const mutations = {
getSpeceficArticle(state, payload) {
return state.articles[payload]
},
}
export const actions = {
getSpeceficArticle({ commit }, payload) {
commit('getSpeceficArticle', payload)
},
}
and here my component codes:
<template>
<div class="article">
{{ getSpeceficArticle() }}
<div class="article__banner">
<img src="" alt="" />
</div>
<div class="article__text">
<p></p>
</div>
</div>
</template>
<script>
export default {
name: 'HomeArticlesArticle',
data() {
return {
item: '',
}
},
// computed: {},
methods: {
async getSpeceficArticle() {
return await this.$store.dispatch('articles/getSpeceficArticle', 0)
},
},
}
</script>
actions are used to update the state they are like mutations but the main difference between them is that actions can include some asynchronous tasks, if you want to get a specific article at given index you should use a getter named getArticleByIndex :
export const getters = {
getArticles(state) {
return state.articles
},
getArticleByIndex:: (state) => (index) => {
return state.articles[index]
}
}
then define a computed property called articleByIndex :
<script>
export default {
name: 'HomeArticlesArticle',
data() {
return {
item: '',
}
},
computed: {
articleByIndex(){
return this.$store.getters.articles.getArticleByIndex(0)
}
},
methods: {
},
}
</script>
#Mohammad if you find yourself using a lot of getters/actions etc from Vuex and they're starting to get a little wordy, you can bring in mapGetters from Vuex and rename your calls to something a little more convenient. So your script would become,
<script>
import { mapGetters } from 'vuex'
export default {
name: 'HomeArticlesArticle',
data() {
return {
item: '',
}
},
computed: {
articleByIndex(){
return this.getArticleByIndex(0)
}
},
methods: {
...mapGetters({
getArticleByIndex: 'articles/getArticleByIndex',
})
},
}
</script>
You can add ...mapGetters, ...mapActions to your computed section also.
since there is no web service call in vuex action, try to remove async and await keywords from the component.
Later when you add a webservice call than you can wrap action body in new Promise with resolve and reject and then you can use async and await in component. let me know if this works for you.

How to use composition API to create a new component in vue3?

When we use vue2 to create API, we just follow options API like below:
data are in data
methods are in methods
<script>
export default {
name: 'demo',
components: {},
filter:{},
mixins:{},
props: {},
data(){
return{
}
},
computed:{},
watch:{},
methods: {},
}
</script>
But the vue3 changed, how should I build a component with vue3 composition API?
Some example say that I should import reactive etc. From vue first and put all codes in setup(){}?
Some example show that I can add setup to <script>?
Please give me an example.
ok bro , Composition Api works like that:
<script>
import { fetchTodoRepo } from '#/api/repos'
import {ref,onMounted} from 'vue'
export default {
setup(props){
const arr = ref([]) // Reactive Reference `arr`
const getTodoRepo = async () => {
arr.value = await fetchTodoRepo(props.todo)
}
onMounted(getUserRepo) // on `mounted` call `getUserRepo`
return{
arr,
getTodoRepo
}
}
}
</script>
There are two ways to create a component in vue3.
One:<script> + setup(){},such as this:
<script>
import { reactive, onMounted, computed } from 'vue'
export default {
props: {
title: String
},
setup (props, { emit }) {
const state = reactive({
username: '',
password: '',
lowerCaseUsername: computed(() => state.username.toLowerCase())
})
onMounted(() => {
console.log('title: ' + props.title)
})
const login = () => {
emit('login', {
username: state.username,
password: state.password
})
}
return {
login,
state
}
}
}
</script>
Two:use <script setup="props">
loading....