Method --> Action --> Mutation --> State issue - vue.js

My function that's supposed to iteratively insert JSON into each element of a particular level of nested JSON using parameters from itself is duplicating the first object returned and inserting it into each nested parameter. See screenshots and code below to get a feel for what I'm doing.
Basically I have a nested JSON object within the state, and need to iteratively append to a nested property of each 'row' of the existing object using an action that returns JSON from an API then a mutation that updates the state.
The problem lies within the mutation I think. I've deduced my function is duplicating the first JSON object returned from the API. See Action and Mutation functions below, along with API function and structure of JSON.
http://api.capecodcommission.org/docs/index.html#sumofannualcapitaltotalsfortreatment
Function within a particular component run using v-for:
methods: {
costTotal () {
return this.updateFinTotals(this.treatment.treatmentId,this.costType.treatTotal,this.costType.financeOption,this.treatment.relativeStartYear,this.costType.finDur,this.costType.prinFor)
}
}
Running function, viewing JSON:
<td class="text-center">
{{ costTotal }}
{{ costType.annualized.sumOfAnnualCapitalTotals }}
</td>
V-for Loop:
<tbody>
<tr v-for="(index, costType) in treatment.costTypes | filterBy 'true' in 'financeable'" is="cost-type-table-row-finance" :cost-type="costType"></tr>
</tbody>
API function:
getSumOfAnnualCapitalTotals (treatmentId, costToFinance, financeOption, relativeStartYear, financeDuration, principalForgivenessRate) {
let data = {
treatmentId: treatmentId,
costToFinance: costToFinance,
financeOption: financeOption,
relativeStartYear: relativeStartYear,
financeDuration: financeDuration,
principalForgivenessRate: principalForgivenessRate
}
return Vue.http.post(API_ROOT + 'sumOfAnnualCapitalTotalsForTreatment', data)
}
Action: Pulls JSON from API, dispatches mutation function.
export const updateFinTotals = function ({ dispatch, state }, treatmentId, costToFinance, financeOption, relativeStartYear, financeDuration, principalForgivenessRate) {
api.getSumOfAnnualCapitalTotals( treatmentId, costToFinance, financeOption, relativeStartYear, financeDuration, principalForgivenessRate ).then(function (response) {
dispatch('UPDATE_ANNUALIZED', response.data)
}, function (response) {
console.log(response)
})
}
Mutation: Updates state with JSON.
UPDATE_ANNUALIZED (state, annualized) {
for (var i = 0; i < state.scenario.treatments.length; i++) {
for (var j = 0; j < state.scenario.treatments[i].costTypes.length; j++) {
state.scenario.treatments[i].costTypes[j]["annualized"] = annualized
}
}
}
EDIT: PICS BELOW
Component: http://i.imgur.com/lcS5Fgo.jpg
JSON Structure: http://i.imgur.com/AsANZOp.jpg

Well, your API returns one value (for one cost-type of one treatment)
Example from linked API docs:
{"sumOfAnnualCapitalTotals":150374.9849625}
but you assign it to all cost-types of all treatments with the for loop in the mutation:
for (var i = 0; i < state.scenario.treatments.length; i++) {
for (var j = 0; j < state.scenario.treatments[i].costTypes.length; j++) {
state.scenario.treatments[i].costTypes[j]["annualized"] = annualized
}
}
what you should be doing is pass the treatmentID and cost type (which, I sherlock-holmed, is called costToFinance) from the action to the mutation, then filter the state for the matching object, and update only that:
UPDATE_ANNUALIZED (state, treadmentId, costToFinance, annualized) {
const treatment = state.treatments.find((t) => t.treatmentId === treatmentId)
const currCostType = treatment.costTypes.find((costType) => costType.Id === costToFinance)
currCostType.annualized = annualized
}
The problem is that your JSON doesn't seem to have any ID for the costTypes to find it by, but that's for you to figure out.

Related

Changes made to a computed array won't show after a prop binding

I'm quite new to vue and right now I'm trying to figure out how to make changes to a computed array and make an element react to this change. When I click the div element (code section 4), I want the div's background color to change. Below is my failed code.
Code section 1: This is my computed array.
computed: {
arrayMake() {
let used = [];
for (let i = 0; i < 5; i++) {
used.push({index: i, check: true});
}
return used;
Code section 2: This is where I send it as a prop to another component.
<test-block v-for="(obj, index) in arrayMake" v-bind:obj="obj" v-on:act="act(obj)"></card-block>
Code section 3: This is a method in the same component as code section 1 and 2.
methods: {
act(obj){
obj.check = true;
}
Code section 4: Another component that uses the three sections above.
props: ["obj"],
template: /*html*/`
<div v-on:click="$emit('act')">
<div v-bind:style="{ backgroundColor: obj.check? 'red': 'blue' }">
</div>
</div>
Easiest way to achieve this, store the object into another data prop in the child component.
child component
data() => {
newObjectContainer: null
},
onMounted(){
this.newObjectContainer = this.obj
},
methods: {
act(){
// you don't need to take any param. because you are not using it.
newObjectContainer.check = !newObjectContainer.check
}
}
watch: {
obj(val){
// updated if there is any changes
newObjectContainer = val
}
}
And if you really want to update the parent component's computed data. then don't use the computed, use the reactive data prop.
child component:
this time you don't need watcher in the child. you directly emit the object from the method
methods: {
act(){
newObjectContainer.check = !newObjectContainer.check
this.emits("update:modelValue", nextObjectContainer)
}
}
parent component:
data() => {
yourDynamicData: [],
},
onMounted(){
this.yourDynamicData = setAnArray()
},
methods(){
setAnArray(){
let used = [];
for (let i = 0; i < 5; i++) {
used.push({index: i, check: true});
}
return used;
}
}
okay above you created a reactive data property. Now you need the update it if there is a change in the child component,
in the parent first you need object prop so, you can update that.
<test-block v-for="(obj, index) in arrayMake" v-model="updatedObject" :obj="obj"></card-block>
data() => {
yourDynamicData: [],
updatedObject: {}
},
watch:{
updatedObject(val){
const idx = val.index
yourDynamicData[idx] = val
}
}

ngx-pagination angular 8 Observables next page not populating on FE

I have a problem with Observables used in this implementation. I am new to Observables.
I am implementing ngx-pagination on existing component with list of users. Problem is that next page is not populating results (only page 1 has 10 first results) but API call is being made and I can see the results in Network tab.
I think it's something with Observables. Any help will be highly appreciated.
component
pageSize: any;
pageNumber = 1;
count = 0;
ngOnInit() {
this.users$ = this.userapiService.getUsers(this.pageNumber);
}
handlePageChange(event) {
this.pageNumber = event;
this.users$ = this.userapiService.getUsers(this.pageNumber);
}
html
<tr *ngFor="let user of users$.getValue() | paginate: { itemsPerPage: pageSize, currentPage: pageNumber }">
<pagination-controls
(pageChange)="handlePageChange($event)"
previousLabel="Previous"
nextLabel="Next"
></pagination-controls>
service
getUsers(pageNumber: number): Observable<User[]> {
let params = new HttpParams();
params = params.append('id', id).append('pageNumber', (pageNumber - 1).toString())
.append('pageSize', constants.pageSize);
this.httpService.httpGet(this.Endpoint, params)
.subscribe(
(response: User[]) => {
this.users$.next(response
.filter(user => user
));
});
return this.users$;
}
As the author of RxJS 5 says, using getValue() in observable chains is a sign of you're doing something wrong.
In this scenario, users$ is an observable. To get the fresh values of the observable, we need to subscribe and listen. We do this with the async pipe in Angular's HTML side as follows:
<tr *ngFor="let user of (users$ | async) | paginate: { itemsPerPage: pageSize, currentPage: pageNumber }">

Vuejs2 component not updating as parent data changes

I am trying to make a simple product catalogue using vuejs2.
All my data is stored in an object array, I then have a subset of this data which is what the product catalogue uses to display each product.The subset is used for pagination.
When a user switches pages it clears and pushes the next set of data into the array.
This automatically makes the product component display the new data.
I have issues with the following, each product has multiple colours which are stored as a comma delimited string eg (white, blue, red)
I am trying to make that information appear as a drop down list of options beside each product.
This works until I switch to the next page of data, all other details update except the colour drop downs, which only reflect the previous set of data.
My product list is stored in an object array like:
var obj = {
productID: productID,
product: title,
gender: gender,
colour: colour,
cost: cost,
size: size,
description: description
}
productArray.push(obj);
I then have several components that display this array of data:
Vue.component('product-list', {
template: '<ul id="productList">'+
'<product :productID="product.productID" v-for="product in products">'+
'<h4>Colour</h4><colourSelect :colours="product.colour" :productID="product.productID"></colourSelect>' +
'<h4>Gender</h4><span class="genderSpan"><p v-bind:id="getID(product.productID)">{{product.gender}}</p></span>' +
'</product></ul>',
data: function() {
return {
products:
paginatedArray
};
},
Vue.component('colourSelect', {
props: ['productID', 'colours'],
template: '<select v-bind:id="getID()" class="form-control input-xs"><colourOption v-for="colourItem in colourArray"></colourOption></select>',
data: function () { //split string based into array
var newArray = [];
var optionsArray = this.colours.split(',');
for (i = 0; i < optionsArray.length; i++) {
var obj = {
colour: optionsArray[i]
}
newArray.push(obj)
}
return {
colourArray: newArray
};
},
methods: {
getID: function (test) {
return 'colourSelect' + this.productID;
}
}
});
Vue.component('colourOption', {
props:['options'],
template: '<option><slot></slot></option>'
});
Within the app section of vuejs I have the following methods that do the pagination:
buildPages: function () {
for (i = 1; i < this.listLength() /this.totalPage ; i++) {
this.pages.push(i);
}
var page = this.currentPage * this.totalPage;
for (i = page; i < page + this.totalPage ; i++) {
paginatedArray.push(productArray[i]);
}
},
listLength: function () {
var listTotal = productArray.length;
return listTotal
},
changePage: function (number) {
this.currentPage = number
var page = this.currentPage * this.totalPage;
//paginatedArray = [];
var count = 0;
for (i = page; i < page + this.totalPage ; i++) {
if (typeof productArray[i] !== 'undefined') {
paginatedArray.splice(count, 1, productArray[i])
}
count++
}
},
productArray is the main array storing data, paginatedArray is the subset of data that the product component works off.
The issue appears to be within the colourSelect component, within its "data" section it splits the colour data and returns it as an colourOption component into the select, but won't update when the paginatedArray changes.
The colourSelect component does however appear to actually get passed the correct data, as getID method updates correctly. Its just the data section which is not being re-rerun.
This is my first vuejs site, anyone have any ideas around this?
Thanks
You should make the colourArray as a computed property, as the data block of component gets executed only once and later change of props will not update colourArray .
Vue.component('colourSelect', {
props: ['productID', 'colours'],
template: '<select v-bind:id="getID()" class="form-control input-xs"><colourOption v-for="colourItem in colourArray"></colourOption></select>',
computed:{
colourArray: function () { //split string based into array
var newArray = [];
var optionsArray = this.colours.split(',');
for (i = 0; i < optionsArray.length; i++) {
var obj = {
colour: optionsArray[i]
}
newArray.push(obj)
}
return newArray
}
},
methods: {
getID: function (test) {
return 'colourSelect' + this.productID;
}
}
});

Reactive computed data in VueJs 2.0

I have a paginated record set from a http response and want to further implement client side pagination on return paginated record set, thus I have the following component markup
<td v-for="item in items">....</td> // only print 5 at a time
and in the default....
{
data: { return {
itemsData:[] // populated from RESTful data in increments of 20
, offset: 0 // for internal pagination
} },
computed: {
items: function(){
return this.itemsData.slice(this.offset, 5); // re-populate over time as offset changes
}
},
methods: {
getItems: function() {
this.$http.get('/api/items/?page=' + this.page).then(response=>
{
this.itemsData = response.data.data; // json array and I al get back meta data
// for which i use in a mixin to calculate offset and page etc.
// for both client side and server side pagination
}) // fetches records 20 at a time
}
}
.........
If itesmData is populated and then offset is dynamically changed. Shouldn't the component's template re-rendered with a new items collection?
Or should I be using a method instead? e.g.
<td v-for="item in paginated(itemData)">....</td>
{
....
methods: {
paginated: function(items){
var arr=[];
for( var i = this.offset; i < this.offset + 5; i++)
arr.push(item[i]);
return arr;
}
}
How would the template be updated with the new array? Would I need to implement a watcher? on the computed data? or would the offset do?
UPDATE:
I tried to implement the pagination via competed and while I get the template to render the first 5..... in trying to re-render after updating offset does not fire.... arr seems to return empty even thought i am on the second page and offset is yet to reach itemsData.length
Can you iterate through a data array property OUTSIDE of the template? i.e. loop through this.itemsData[i] or this.$data.itemsData[i]???
You need to make following changes in your code:
computed: {
items: function(){
return this.itemsData.slice(this.offset, this.offset + 5); // re-populate over time as offset changes
}
}
As you can see from documentation, slice takes two argument start and end, it will return a portion of an array into a new array object selected from start to end (end not included).

How to access dynamic props inside a child component's ready() in VueJS

I have dynamically bind a parent's data to a child via dynamic props (objectives is an array which is fetched via AJAX):
<objective-selector :objectives="objectives"></objective-selector>
Inside the objective-selector's ready() hook, I wish to do some pre-processing and somehow this.objectives is an observable, not an array:
Vue.component('objective-selector',{
template: '#objective-selector-template',
data:function() {
return {
current_selected:0
}
},
ready:function(){
// set current selected to the first non-active objective
for (var i =0; i < this.objectives.length; ++i) {
if (this.objectives[i].active == false) {
this.current_selected = i;
break;
}
}
},
props: ['objectives']
});
Everything else works fine (I can use the objectives props as array in the template, for instance). How do I access it as an array inside ready()?
Your ready function is fired sequentially before the ajax response comes back from the server. So you need to fire the function afterward.
One easy way to do this is $broadcast when the value has returned from the parent.
compiled: function(){
this.$on('theDataCameBackFromTheServer',function(){
for (var i =0; i < this.objectives.length; ++i) {
if (this.objectives[i].active == false) {
this.current_selected = i;
break;
}
}
});
}
Another solution would be to use watch (this will fire every time objectives changes):
watch: {
objectives: function(){
for (var i =0; i < this.objectives.length; ++i) {
if (this.objectives[i].active == false) {
this.current_selected = i;
break;
}
}
}
}