I have a scenario in Vue 2 where I am initializing API classes as data properties on my component like so:
new Vue({
el: '#my-element'
data: {
apiUrl: apiUrl,
api: new ApiClient(this.apiUrl)
}
})
API Client:
class ApiClient {
constructor(apiUrl) {
this.apiUrl = apiUrl;
}
async getRequest() {
// Perform GET
}
}
This works just fine, but I have scenarios where I need to use two different API clients. If a certain prop gets passed into my component, I want to initialize that data property as secondApi, as an example.
In this scenario, I see myself using the created() hook:
created() {
if (prop === true) {
this.secondApi = new SecondApiClient(this.apiUrl);
}
}
In my data : { } object though, I'm not sure how to initialize this optional secondApi property properly (in the case that the prop is not passed in). Do I just set it as an empty object at first? It will always be a class object like apiClient. Is there an empty class data type?
Yes you can follow one of the below
Approach 1:
data() {
return {
apiUrl: apiUrl,
api: null // or you can even keep it as new ApiClient(this.apiUrl) if that's the default value
}
},
props: ['certainProperty'],
created() {
if (this.certainProperty === true) { // certainProperty is a prop
this.api = new SecondApiClient(this.apiUrl);
} else this.api = new ApiClient(this.apiUrl);
}
Sometimes there might be a delay in receiving the props so its better to follow the below approach
Approach 2:
data() {
return {
apiUrl: apiUrl,
api: null // or you can even keep it as new ApiClient(this.apiUrl) if that's the default value
}
},
props: ['certainProperty'],
watch: {
certainProperty(newVal) {
if (newVal === true) { // certainProperty is a prop
this.api = new SecondApiClient(this.apiUrl);
} else this.api = new ApiClient(this.apiUrl);
}
}
Note: You need to pass the props from parent component like
<child-component :certainProperty="true" />
Related
I have component MyComponent.vue where I have data value that constantly changes. I want to pass this value to javascript file(js file should know about changes of value everytime)
Why do I do that? Because my regular js file is a service layer for axios methods. I can import this file in many other components. The file contains axios methods and urls are dynamic.
I want those urls depend on data variable. This data variable comes from MyComponent.js
So the main goal is to make dynamic urls of axios that depend on data variable
I tried some code but it doesn't work, because js file(CategoryService.js) know nothing about this.categoryNumber.
MyComponent.vue:
<script>
export default {
data() {
return {
categoryNumber: 1
}
}
}
</script>
CategoryService.js
import http from "../../../http-common";
let category = "category1";
if (this.categoryNumber === 1) {
category = "category1";
} if (this.categoryNumber === 2) {
category = "category2";
}
class CategoryService {
get(id) {
return http.get(`/${category}/${id}`);
}
update(id, data) {
return http.put(`/${category}/${id}`, data);
}
create(data) {
return http.post(`/${category}`, data);
}
delete(id) {
return http.delete(`/${category}/${id}`);
}
getAll() {
return http.get(`/${category}/all`);
}
}
export default new CategoryService();
So with a bit of refactoring, you could easily get this working.
First of all, I would put the if/else logic of your class into it.
For convenience and scalability, I would use a Vuex store that will keep track of your categoryNumber and share it accross all your components.
Then I would bind my service to my Vue instance so I can easily access it in all my components as well as the store and I would pass the latter to my class as a parameter.
For the last part, I don't know the logic in the http-common file so the code I will show you is a bit nasty. But depending on wether or not you bound 'http' to axios, you could make use of axios interceptors to call the getCategoryNumber() method in every request.
Here's an idea of the implementation I would go for:
const CategoryService = class CategoryService {
constructor(store) {
this._store = store;
this.category = "category1";
}
getCategoryNumber() {
if (this._store.state.categoryNumber === 1) {
this.category = "category1";
}
if (this._store.state.categoryNumber === 2) {
this.category = "category2";
}
console.log(this.category); // for demo puprose
}
get(id) {
this.getCategoryNumber(); // We could use axios request interceptor instead of calling that in every route, but that works !
return http.get(`/${this.category}/${id}`);
}
update(id, data) {
this.getCategoryNumber();
return http.put(`/${this.category}/${id}`, data);
}
create(data) {
this.getCategoryNumber();
return http.post(`/${this.category}`, data);
}
delete(id) {
this.getCategoryNumber();
return http.delete(`/${this.category}/${id}`);
}
getAll() {
this.getCategoryNumber();
return http.get(`/${this.category}/all`);
}
}
const store = new Vuex.Store({
state: {
categoryNumber: 1
},
mutations: {
setCategoryNumber(state, payload) {
state.categoryNumber = payload;
}
}
});
// Bind your service to the Vue prototype so you can easily use it in any component with 'this.$service'
// pass it the store instance as parameter
Vue.prototype.$service = new CategoryService(store);
new Vue({
el: "#app",
store, // dont forget to bind your store to your Vue instance
methods: {
updateCategoryNumber() {
// Put here any logic to update the number
this.categoryNumber = this.categoryNumber === 1 ? 2 : 1;
this.checkServiceCategoryValue();
},
checkServiceCategoryValue() {
// for demonstration purpose
this.$service.getCategoryNumber();
}
},
computed: {
// Look for the store value and update it
categoryNumber: {
get() {
return this.$store.state.categoryNumber;
},
set(value) {
this.$store.commit("setCategoryNumber", value);
}
}
}
});
<div id="app">
<h2>number: {{ categoryNumber }}</h2>
<button type="button" #click="updateCategoryNumber()">
updateCategoryNumber
</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex#2.0.0"></script>
Thanks to #Solar
I just added one more parameter for all urls and put the number of category to it
CategoryService.js:
class CategoryOneService {
get(id, category) {
return http.get(`/${category}/${id}`);
}
getAll(category) {
return http.get(`/${category}/all`);
}
}
functions.js:
let catNum = "";
function getQuestion() {
if (this.categoryNumber === 1) {
catNum = "category1";
}
if (this.categoryNumber === 2) {
catNum = "category2";
}
let questionId = this.questionNumber;
CategoryOneService.get(questionId, catNum)
.then(response => {
this.question = response.data.question;
this.answer = response.data.answer;
})
.catch(error => {
console.log(error);
});
}
So, I attempting to update some data from component every time the state in vuex not null. I set up an API routes with laravel that returns user information after they logged in.
API routes:
Route::group(['middleware' => ['auth:api']], function () {
Route::get('profil', 'Api\UserController#profil')->name('profile'); // will returning user info
}
Vuex:
export default new Vuex.Store({
state: {
token: localStorage.getItem('token') || "",
user: {}
},
getters: {
isAuth: state => {
return state.token != "" && state.token != null
}
},
mutations: {
SET_TOKEN(state, payload) {
state.token = payload
},
SET_AUTH_USER(state, payload) {
state.user = payload
}
}
})
So in my App.vue, in created method, I commit SET_AUTH_USER with the http response as the payload if the token was exist.
App.vue:
<template>
<div id="app-layout">
<section-advices></section-advices>
</template>
<script>
import SectionAdvices from "./SectionAdvices"
export default {
name: "app-layout",
components: {
SectionAdvices
},
created() {
if (this.$store.state.token !== (null || "")) { //check token in vuex state
this.$http
.get("/profil")
.then(res => {
if (res.status === 200) {
this.$store.commit("SET_AUTH_USER", res.data.data); //set $store.state.user with response
} else {
this.$store.commit("SET_AUTH_USER", null); // set with null
}
})
.catch(err => {
console.log(err);
});
}
}
}
</script>
so far, everything works fine. every time I refresh the page, as long as there's a token in my local storage, the user object will always have the user information.
SectionAdvices.vue:
<template>
<section class="advices">
<input type="text" v-model="name">
<input type="text" v-model="email">
</section>
</template>
<script>
import { mapState, mapGetters, mapActions, mapMutations } from "vuex";
export default {
name: "section-advices",
data(){
return{
name: null,
email: null
}
},
computed:{
...mapState(["user"]),
...mapGetters(["isAuth"]),
},
created() {
if(this.isAuth) { //its returning true. the codes below was executed
this.name = this.user.name // gives name data with "undefined"
this.form.email = this.user.email // it's "undefined" too
}
}
}
</script>
Both name and email in SectionAdvices component was set as "undefined" although in Vue Dev tools, the user object does have these values. Did I call the api in App.vue inside wrong life cycle?
Can you try to use getters to take state data? Because of lifecycles. Getters are setting first when component page rendering
I found th solution, which is:
as what #dreijntjens suggested, I added watcher in my "SectionAdvices.vue"
...
watch: {
// watching for store.state.user changes
user: {
handler: "fillForm", // call the method
immediate: true // this watcher will exec immediately after the comp. created
}
},
methods: {
fillForm() {
if(this.isAuth) { // this is not necessary, just want to make sure. Once isAuth === true, codes below will be executed
this.form.name = this.user.name
this.form.email = this.user.email
}
}
}
So, the real problem is, when SectionAdvices.vue created and fetching data, store.state.user still an empty object. My API calls runs after this step, which is pointless. So, it's true I need watcher, to see any changes in user state and update the local data inside its component after that.
I'm very new to Vuex, and trying to assign a value to a Vuex state, (state.map.status.isReady for this one).
However, I want to make my code reusable, so I created a function changeMapStatus(state, key, value) in order to achieve that.
This function modifies the property state.map.status.key to value it received.
However, when I call the mutation with this.$store.commit('changeMapStatus', 'isReady', true) from a component file, it simply removes the state.map.status.isReady and that property becomes undefined.
On the another hand, if I change the function to
changeMapStatus(state, value) {
state.map.status.isReady = value;
}
it somehow works.
Can you help me which point I get it wrong?
Thanks so much!
store.js (Vuex)
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
map: {
status: {
isReady: false,
},
},
},
mutations: {
changeMapStatus(state, key, value) {
state.map.status[key] = value;
}
},
});
According to Vuex official docs, mutation takes 2 parameters state and payload. You can use the spread operator to get values from the payload.
changeMapStatus(state, {key, value}) {
state.map.status[key] = value;
}
this.$store.commit('changeMapStatus', {key: 'isReady', value: true})
Or else you can use it like this
changeMapStatus(state, payload) {
state.map.status = {
...state.map.status,
...payload,
}
}
this.$store.commit('changeMapStatus', { isReady: true });
You could pass an object as parameter that contains key and value as follows :
changeMapStatus(state, myObj) {
state.map.status[myObj.key] = myObj.value;
}
and call it like:
this.$store.commit('changeMapStatus', {key:'isReady', value:true})
I working on a schedule with VUE, I'm really new with VUE and with JS. I created VUE component that adds some new properties into the object which already in state and then I take this object with getters and render inside my another VUE component, but unfortunately new properties render only after reloading.
Here is snippet from my component where I add new property
methods: {
...mapActions({addDateToState: 'addDate'}),
addDate () {
this.date = this.startTime.time;
//date from datepicker
this.addDateToState({date:this.date, year:this.year, time:this.event.time, name:this.event});
// object need to add
},
}
Here is a snippet from state
const state = {
schedule: {}
}
Here is actions
addDate ({commit}, date) {
commit('ADD_DATE', date)
}
And here is the mutation which does all the work
ADD_DATE (state, date) {
if (typeof state.schedule[date.year] === 'undefined') {
state.schedule[date.year] = {};
}
if (typeof state.schedule[date.year][date.date] === 'undefined') {
state.schedule[date.year][date.date] = {};
}
if (typeof state.schedule[date.year][date.date][date.time] === 'undefined') {
state.schedule[date.year][date.date][date.time] = [];
}
state.schedule[date.year][date.date][date.time].push(date.name)
//interesting that this properties are reactive, so I see chenges when pushh new element to the array, but don't see if it is new property on the object
console.log(state.schedule)
//here I see the schedule which already has new properties so mutation works
}
Getters
const getters = {
schedule() {
return state.schedule;
}
}
And here is computed property which get the schedule from getters
computed: {
...mapGetters([
'schedule'
])
}
So the problem that I can't make this getter reactive, but I see that state was changed. Can someone help me?
You need to use Vue.set/this.$set if you are adding new property. See vue docs
me, when i need a reactive getter in vue a do that:
computed: {
getSchedule: function () { return this.schedule; }
}
I have a list of Tickets which works fine (companytickets), and when clicked.. it opens up a details page (companyticket) for that specific ticket, passing the id to the component.
problem is i can't find out how to access this prop parameter in the created event, since it's not accessable through "this".
companytickets.vue :
viewTicket: function(ticket){
this.$router.push('/companyticket/' + ticket.Id)
// works : this redirects to http://localhost:8180/companyticket/3
}
companyticket.vue
export default {
name: 'CompanyTicket',
props: {
id: {
type: Number,
required: true
}
},
created() {
this.$store.dispatch('getCompanyTicket', this.id)
// ERROR : this.id is undefined...
console.log("Created here :")
}
}
route config
{ path: '/companyticket/:id', component: CompanyTicket, props: true }
Scenario
this.id is "undefined"
when using this.$route.params.id i get the correct id parameter, but in some weird way it claims to use "companytickets/2" (which is the parent page). The Correct should be companyticket/2.
Screenshot of Chrome Dev :
Use object-style or payload for passing params to actions.
Change:
this.$store.dispatch('getCompanyTicket', this.id)
To:
this.$store.dispatch("getCompanyTicket", {
id: this.id
})
Now your files looks like this:
companyticket.vue
created() {
this.$store.dispatch("getCompanyTicket", {
id: this.id
})
}
store.js
actions: {
getCompanyTicket({ commit }, { id }) {
console.log("ID is available now-->", id)
}
}
Vuex
Since you're using Vuex state management pattern, that would be another approach to share data between component.
It allow parent-child communication and same for child-parent (sharing data with props allow only parent-child communication). Inject store into to your root component:
const app = new Vue({
el: '#app',
// provide the store using the "store" option.
// this will inject the store instance to all child components.
store,
})
This is everything you need in your store object:
var store = new Vuex.Store({
state: {
ticketID: Number
},
mutations: {
UPDATE_TICKET_ID(state, ticketId) {
state.ticketId = ticketId;
}
},
actions: {
getCompanyTicket({ commit, state }, { id }) {
commit("UPDATE_TICKET_ID", id)
}
}
}
Also if you want to update state:
The only way to actually change state in a Vuex store is by committing
a mutation
Any property from state will be available in every component:
console.log(this.$store.state.ticketId)