I'm using an AngularJS service to store data from multiple pages, to be submitted together. See the code below.
In my Chrome console if I observe checkoutData.shipping, I get back the correct object with data. If I observe checkoutData.data I get an empty object, where it's shipping property is blank.
These should be pointing at the same object, right? Why would it work with .shipping and not using .data? The reason it's set up this way is that the shipping page only cares about .shipping, while the final page needs everything in .data.
(function() {
angular.module('app').factory('checkoutData', [function() {
var data = {
carrierId: null,
serviceTypeId: null,
shippingCost: {},
billingOptionId: null,
shipping: {},
billing: {},
cart: null
};
var inputForms = {};
var options = {
shippingOptions: null,
billingOptions: null,
selectedShippingOption: null
};
var staticContent = {
billing: {},
cart: {},
shipping: {}
};
return {
data: data,
shipping: data.shipping,
inputForms: inputForms,
cart: data.cart,
billingOptionId: data.billingOptionId,
billingOptions: options.billingOptions,
carrierId: data.carrierId,
serviceTypeId: data.serviceTypeId,
shippingOptions: options.shippingOptions,
staticContentBilling: staticContent.billing,
staticContentCart: staticContent.cart,
staticContentShipping: staticContent.shipping,
selectedShippingOption: options.selectedShippingOption,
setValid: function(formName, valid) {
inputForms[formName].valid = valid;
},
initializeForm: function(formName) {
inputForms[formName] = {};
},
formInitialized: function (formName) {
return inputForms[formName] != null;
}
}
}]);
})();
One suggestion to make things easier is separate your data model(s) from your methods. And there's no need to try to keep multiple references to the same object within the same factory. There's nothing wrong with doing for example ng-model="checkoutModule.data.shipping.firstName". Is it wordier? Yes. Is it wrong? No.
So maybe something like
angular.module('app').factory('checkoutData', [function() {
return {
dataModel: {
carrierId: null,
serviceTypeId: null,
shippingCost: {},
shipping: {}, // This should be databound to an object from "shippingOptions", removing the need for "selectedShippingOption"
billing: {}, // This should be databound to an object from "billingOptions", removing the need for "billingOptionId"
cart: null
},
options: {
shippingOptions: null,
billingOptions: null
},
inputForms: {}
};
}]);
for your data and
angular.module('app').factory('checkoutModule', ['checkoutData', function(checkoutData) {
return {
data: checkoutData.dataModel,
options: checkoutData.options,
inputForms: checkoutData.inputForms,
setValid: function(formName, valid) {
checkoutData.inputForms[formName].valid = valid;
},
initializeForm: function(formName) {
checkoutData.inputForms[formName] = {};
},
formInitialized: function (formName) {
return checkoutData.inputForms[formName] != null;
}
}
}]);
for the factory that ties it all together.
From what I can see there is no way of setting the value of data.shipping other than using something like:
checkoutData.shipping = {"country" : "Sweden"};
This will result in checkoutData.shipping pointing to a new object and checkoutData.shipping will return that object:
{"country" : "Sweden"};
but
checkoutData.data will return the original empty shipping object since we haven't updated that value.
If you instead create a function for setting the shipping value in the checkoutData service:
setShipping : function(s){
data.shipping = s
},
and use that for setting the shipping data, you will get the values you want from checkout.data and checkout.shipping.
Have a look at this for demonstration: jsfiddle
Related
I am trying to call a getter method and it's not getting called for some reason. If I console.log the store I can see that it's undefined:
This is where I'm calling the getter method:
computed: {
client() {
console.log(this.$store); //see above screenshot
console.log(this.$route.params.id); //shows a valid value.
//nothing seems to happen after this point, console.log in the below getter doesn't happen.
return this.$store.getters['clients/clientById', this.$route.params.id];
}
},
here's my getter in clients.js module:
getters: {
clients(state) {
return state.clients;
},
hasClients(state) {
return state.clients.length > 0;
},
clientById(state, id) {
console.log('test'); //this doesn't happen
return state.clients.find(client => client.id === id);
}
}
The first 2 getter methods are working fine, using the same syntax as what I'm doing when I'm calling the clientById getter.
What I'm trying to accomplish is to have an array of client objects, and then when a user clicks on a client in the client list, I grab the ID out of the route params and the appropriate client data is displayed on the page. I'd appreciate any guidance on whether I'm approaching this in the right way as well as I'm new to Vue.
state() {
return {
clients: [
{
id: null,
client_name: '',
address: '',
city: '',
state: '',
zip:'',
created_at: '',
updated_at: '',
deleted_at: null
},
]
};
},
UPDATE:
I'll provide my entire clients.js module in case something is off with that. Everything else seems to be working fine, so not sure if this is related or not. This is an updated version of the getter where I changed it to an arrow function based on your feedback. When I do this, I get another error: TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at Function.
I've also tried hard-coding the ID within the getter method and taking it out of the passed-in ID parameter, and that seems to work, but is returning undefined, so it's still not getting a value from state.
import axios from "axios";
export default {
namespaced: true,
state() {
return {
isLoading: false,
clients: [
{
id: null,
client_name: '',
address: '',
city: '',
state: '',
zip:'',
created_at: '',
updated_at: '',
deleted_at: null
},
]
};
},
mutations: {
setLoadingStatus(state, status) {
state.isLoading = status;
},
setClients(state, clients) {
state.clients = clients;
}
},
actions: {
async fetchClients(context) {
context.commit('setLoadingStatus', true);
try {
const resp = await axios.get('http://localhost/api/clients');
context.commit('setLoadingStatus', false);
context.commit('setClients', resp.data);
} catch(e) {
console.log(e);
}
}
},
getters: {
clients(state) {
return state.clients;
},
hasClients(state) {
return state.clients.length > 0;
},
clientById: (state) => (id) => {
return state.clients.find(client => client.id === id);
}
}
};
I am a newbie in vue.js. I have a problem with side effect in computed property. I'm not sure why i'm getting an unexpected side effect in computer property error with the code below. ESlint shows me this error in console. I found what does it mean, but I dont have any idea how to change. Any ideas?
export default {
name: "Repaid",
components: {
VueSlideBar
},
data() {
return {
response: {},
slider: {
lineHeight: 8,
value: 2000,
data: []
},
days: {},
monthCounts: [5],
currentMonthCount: 5,
isLoading: false,
errorMessage: "",
validationError: ""
};
},
computed: {
finalPrice() {
const index = this.monthCounts.indexOf(this.currentMonthCount);
this.days = Object.keys(this.response.prices)[index]; =>this is my side effect
const payment = this.response.prices[this.days]
[this.slider.value].schedule[0].amount;
}
},
I read that i should add slide() to my variable that.days, to mutate the original object. Or maybe I should carry them to the methodes.
EDIT
I carried all my calculations to methods and trigger a function in computed property.
Now it looks like this:
methods: {
showPeriod() {
const index = this.monthCounts.indexOf(this.currentMonthCount);
this.days = Object.keys(this.response.prices)[index];
const payment = this.response.prices[this.days][this.slider.value]
.schedule[0].amount;
return "R$ " + payment;
},
}
computed: {
finalPrice() {
if (!this.response.prices || this.monthCounts === []) {
return "";
}
return this.showPeriod();
}
},
It's coming from "eslint-plugin-vue" and the link for that rule is below,
https://eslint.vuejs.org/rules/no-side-effects-in-computed-properties.html
Either you override this rule in your eslint rules file or you can simply turn off the eslint for this specific line like below
this.days = Object.keys(this.response.prices)[index]; // eslint-disable-line
--
One more thing (Not related to your question) is that you need to return some value in computed.
I'm trying to build a small application in VueJs,
Following is my data set:
data(){
return {
pusher: '',
channel:'',
notify: [],
notifications: '',
notificationsNumber: '',
}
},
where I'm having an axios call in created property of components as:
axios.get('api/notifications', {headers: getHeader()}).then(response => {
if(response.status === 200)
{
this.notify = response.data.notifications
this.notificationsNumber = this.notify.length
}
}).catch(errors => {
console.log(errors);
})
I'm having pusherJs implemented, so I'm having following code:
this.pusher = new Pusher('xxxxxxxx', {
cluster: 'ap2',
encrypted: true
});
var that = this
this.channel = this.pusher.subscribe('stellar_task');
this.channel.bind('company_info', function(data) {
console.log(data.notification);
that.notifications = data.notification
});
Once the value is being obtained from pusher I want to push this to my array notify as watch property, something like this:
watch: {
notifications(newValue) {
this.notify.push(newValue)
this.notificationsNumber = this.notificationsNumber + 1
}
}
So the problem is the data format which I'm receiving through pusher is in object form and push function is not getting implemented in this:
Screenshot:
Help me out with this.
I'm making an assumption that response.data.notifications is an Array Like Object.
So all you have to do is:
this.notify = [...response.data.notifications];
My code doesn't create a new property under the child element of knockout viewmodel that is mapped by knockout.mapping.fromJS.
I have:
//model from Entity Framework
console.log(ko.mapping.toJSON(model));
var viewModel = ko.mapping.fromJS(model, mappingOption);
ko.applyBindings(viewModel);
console.log(ko.mapping.toJSON(viewModel));
The first console.log outputs:
{
"Id": 0,
"CurrentUser": {
"BoardIds": [
{
"Id": 0
}
],
"Id": 1,
"UserName": "foo",
"IsOnline": true
},
"Boards": []
}
And then the mappingOption is:
var mappingOption = {
create: function (options) {
var modelBase = ko.mapping.fromJS(options.data);
modelBase.CurrentUser.UserName = ko.observable(model.CurrentUser.UserName).extend({ rateLimit: 1000 });
//some function definitions
return modelBase;
},
'CurrentUser': {
create: function (options) {
options.data.MessageToPost = ko.observable("test");
return ko.mapping.fromJS(options.data);
}
}
};
I referred to this post to create the custom mapping, but it seemed not working as the second console.log outputs the same JSON to the first one.
Also, I tried to create nested mapping option based on this thread and another one but it didn't work too.
var mappingOption = {
create: function (options) {
//modelBase, modifing UserName and add the functions
var mappingOption2 = {
'CurrentUser': {
create: function (options) {
return (new(function () {
this.MessageToPost = ko.observable("test");
ko.mapping.fromJS(options.data, mappingOption2, this);
})());
}
}
}
return ko.mapping.fromJS(modelBase, mappingOption2);
}
};
How can I correctly add a new property to the original viewmodel?
From the mapping documentation for ko.toJS (toJS and toJSON work the same way as stated in the document)
Unmapping
If you want to convert your mapped object back to a regular JS object, use:
var unmapped = ko.mapping.toJS(viewModel);
This will create an unmapped object containing only the properties of the mapped object that were part of your original JS object
If you want the json to include properties you've added manually either use ko.toJSON instead of ko.mapping.toJSON to include everything, or use the include option when first creating your object to specify which properties to add.
var mapping = {
'include': ["propertyToInclude", "alsoIncludeThis"]
}
var viewModel = ko.mapping.fromJS(data, mapping);
EDIT: In your specific case your mapping options are conflicting with each other. You've set special instructions for the CurrentUser field but then overridden them in the create function. Here's what I think your mapping options should look like:
var mappingOption = {
'CurrentUser': {
create: function (options) {
var currentUser = ko.mapping.fromJS(options.data, {
'UserName': {
create: function(options){
return ko.observable(options.data);
}
},
'include': ["MessageToPost"]
});
currentUser.MessageToPost = ko.observable("test");
return ko.observable(currentUser).extend({ rateLimit: 1000 });
}
}
};
and here's a fiddle for a working example
I have an API which return a json array object, right now, I get the json in my Controller like this and it works just fine :
angular.module('lyricsApp', [])
.controller('LyricsController', ['$scope', 'ApiCall', function ($scope, ApiCall) {
$scope.lyrics = {
id: "",
songName: "",
singerName: "",
writtenBy: "",
lyricText: "",
isEnable: "",
created_at: "",
updated_at: ""
};
$scope.searchLyric = function () {
var result = ApiCall.GetApiCall().success(function (lyrics) {
$scope.lyrics.id = lyrics.data.id
$scope.lyrics.singerName = lyrics.data.singerName;
$scope.lyrics.songName = lyrics.data.songName;
$scope.lyrics.writtenBy = lyrics.data.writtenBy;
$scope.lyrics.lyricText = lyrics.data.lyricText;
$scope.lyrics.isEnable = lyrics.data.isEnable;
$scope.lyrics.created_at = lyrics.data.created_at;
$scope.lyrics.updated_at = lyrics.data.updated_at;
});
}
}])
But I think this is not a good practice, I already try this :
var result = ApiCall.GetApiCall().success(function (lyrics) {
$scope.lyrics=lyrics.data;
});
in this case I get undefined value :
console.log($scope.lyrics.id); // show Undefined
So, if you guys can suggest a better way I will be appreciate it.
You are doing the right thing, except for console.log. If your log statement is executed before the assignment is done, you will get the undefined value.
Also I don't see why you would do a var result =
You can simply do
ApiCall.GetApiCall('v1', 'lyrics', '1').success(function (data) {
$scope.lyrics = data.data;
console.log($scope.lyrics.id);
}).error(fucntion(data){
console.log(data);
});