Vue reactivity is always one step behind - javascript

I have the following setup:
A type StringMap which I want to work like Map<> but since I'm using vue2, Map<> reactivity is not possible and I've tried to do it myself via an Array:
import Vue from "vue";
export default class StringMap {
entries: Array<[string, number]>;
constructor(data?: Record<string, number>) {
Vue.set(this, "entries", data ? Object.keys(data).map((key) => [key, data[key]]) : []);
}
get(key: string): number {
return this.entries.find((e) => e[0] === key)?.[1];
}
set(key: string, value: number): this {
const entry = this.entries.find((e) => e[0] === key);
if (entry) {
Vue.set(entry, 1, value);
}
else {
this.entries.push([key, value]);
}
return this;
}
has(key: string): boolean {
return this.entries.some((e, idx, arr) => e[0] === key);
}
delete(key: string): boolean {
const idx = this.entries.findIndex((e, idx, obj) => e[0] === key);
if (idx != -1) {
this.entries.splice(idx, 1);
}
return idx != -1;
}
clear(): void {
Vue.set(this, "entries", []);
}
}
In my Vue-Template, I have an input-field that listens to the #change method and calls calculateTotals()
<input :id="'viewModel.phasenChangeRequests[0].ppmProjektPhaseResource[' + index +'].personalInternPlanFach_PT'"
v-model="viewModel.phasenChangeRequests[0].ppmProjektPhaseResource[index].personalInternPlanFach_PT"
class="form-control"
asp-horizontal="true"
#change="calculateTotals('totalPersonalInternPlanFach_PT')"/>
And the calculateTotals is just looping over the data and summing the fields:
calculateTotals(key: string = null) {
if (this.type === "PhasenCrs") {
let totalPersonalInternPlanFachPt = 0;
for (let year = this.startyear; year < this.startyear + 5; year++) {
const phasen = this.viewModel.phasenChangeRequests;
phasen.forEach((phase) => {
var ressourcenOfYear = phase.ppmProjektPhaseResource.filter(x => x.resourceYear === year)[0];
totalPersonalInternPlanFachPt += parseFloat(ressourcenOfYear.personalInternPlanFach_PT);
});
}
if (key === null) {
console.log(`Key 'totalPersonalInternPlanFach_PT' doesn't exist yet, set it to: ${totalPersonalInternPlanFachPt}`);
this.totals2.set("totalPersonalInternPlanFach_PT", totalPersonalInternPlanFachPt);
} else {
switch (key) {
case 'totalPersonalInternPlanFach_PT':
{
console.log(`set totalPersonalInternPlanFach_PT: ${totalPersonalInternPlanFachPt}`);
this.totals2.set("totalPersonalInternPlanFach_PT", totalPersonalInternPlanFachPt);
}
}
}
}
}
totals2 is a StringMap that is initialized in the created() method:
totals2: StringMap;
async created() {
this.startyear = parseInt(moment(this.startdate, "DD.MM.YYYY").format("YYYY"));
this.projekt = JSON.parse(this.projektjson);
this.totals2 = new StringMap();
await Axios.get(this.url)
.then(res => {
this.viewModel = res.data;
})
.then(() => {
this.calculateTotals();
});
}
When I run it, it initially looks like this:
So as you can see, all fields for each year are summed correctly.
When I now change the value of 2021, the following happens:
But in the console, I can see that the total was calculated correctly:
When I now change the value back to the initial value, it shows this:
So that the previous change is now reflected in the total field but the console is showing the correct result again.
It seems that the value in the view is always one tick behind the real value...
Why is this happening?
Thanks in advance

Related

How to add custom colors to the line chart created using Angular

I have created line chart using Fushionchart in Angular with API data. I have method to add custom color to the chart but it doesn't work. Here I have provide full code to figure the flow. Function pass two parameters and need to set to the label of chart.FYI: *getColorCode() is located in dashboard.service.ts(in below). Here's the full code.
dashboard.component.html
<div *ngIf="multipleConsump" class="chart-card-body p-3">
<app-consumption-summery-chart></app-consumption-summery-chart>
</div>
dashboard.component.ts
getConsumptionData(): void {
this.typeChart.reset();
if (this.trendLogTabIndex == 0 || this.trendLogTabIndex == 1) {
this.tempIntervalid = 2;
} else if (this.trendLogTabIndex == 2) {
this.tempIntervalid = 3;
} else if (this.trendLogTabIndex == 3) {
this.tempIntervalid = 4;
}
this.ltDashboardService
.getCategoryTrends(
this.buildingId,
this.tempIntervalid,
this.intervalTime.from,
this.intervalTime.to,
)
.subscribe(
(res: Object[]) => {
if (res.length) {
const labels = res[0]["data"].map((x) => x.label);
const values = res
.filter((x: any) => this.selectedTypes.indexOf(x.itemId) > -1)
.map((x: any) => {
return {
seriesname: x.name,
data: x.data.map((d) => {
return {
value: d.value,
};
}),
};
});
this.typeChart.setData(labels, values);
}
},
(err) => console.log("Err")
);
}
dashboard.service.ts
getCategoryTrends(building: number, interval: intervalGroup, from:Date, to:Date): Observable<any> {
return this.dataService.get(this.config.endPoints['building-type-consumption-group'], {
startDate: from,
endDate: to,
groupId: GroupBy.MeterType,
serviceTypeId: ServiceType.ELECTRICAL,
buildingId: building,
interval: interval,
siteId: this.config.siteConfigurations.siteId,
dataMode: DataMode.CategorySum,
})
}
Get color code function (meter type is ChartGroupType.METER_TYPES for above function )
Get color code
private getColorCode(chartGroupType: ChartGroupType, itemId: number) {
let item;
let color;
switch(chartGroupType) {
case ChartGroupType.METER:
return '';
case ChartGroupType.BUILDING_CATEGORY:
item = find(this.initialService.navigationStore.buildingCategories, {buildingCategoryID: itemId});
return this.getDefaultColorCode(item);
case ChartGroupType.METER_TYPES:
item = find(this.initialService.navigationStore.meterTypes, {meterTypeID: itemId});
return this.getDefaultColorCode(item);
case ChartGroupType.HT_LOOPS:
return '';
}
}
private getDefaultColorCode(item) {
let color;
if(item === undefined || item.attributes.length === 0) {
return this.config.config.defaultColorCode;
} else {
color = find(
item.attributes,
{
textId: this.config.config.attributes.COLOR_CODE
});
console.log(item + 'chart colors are: ')
return color===null || color === undefined?this.config.config.defaultColorCode:color.value;
}
}

Double for loop without mutating prop, VUE3

I have a 'data' props which say looks like this:
data = [
{
"label":"gender",
"options":[
{"text":"m","value":0},
{"text":"f","value":1},
{"text":"x", "value":null}
]
},
{
"label":"age",
"options":[
{"text":"<30", "value":0},
{"text":"<50","value":1},
{"text":">50","value":3}
]
}
]
In a computed property I want to have a new array which looks exactly like the data prop, with the difference that - for the sake of example let's say - I want to multiply the value in the options array by 2. In plain js I did this before, like this:
data.forEach(item => {
item.options.forEach(option => {
if (option.value !== null && option.value !== 0) {
option.value *= 2;
}
})
});
Now I'm trying to do this in a computed property, with .map(), so it doesn't mutate my data props, but I cant figure out how.
computed: {
doubledValues() {
var array = this.data.map((item) => {
//...
item.options.map((option) => {
//... option.value * 2;
});
});
return array;
}
}
you can use map() method, like so:
computed: {
doubledValues() {
return this.data.map(item => ({...item, options: item.options.map(obj => {
return (obj.value != null) ? { ...obj, value: obj.value * 2 } : { ...obj }
})})
);
}
}
Just copy objects/arrays. It will be something like that
computed: {
doubledValues() {
return this.data.map((item) => {
const resultItem = {...item};
resultItem.options = item.options.map((option) => {
const copyOption = {...option};
if (copyOption.value !== null && copyOption.value !== 0) {
copyOption.value *= 2;
}
return copyOption;
});
return resultItem;
});
}
}

Angular: how to wait for the request response and after that proceed next step in function

I have a little problem with my function, one of the params that I want to set I'm getting from the http request. The problem is that the final data from method below is recalculated in one of the components, and when it happens the result of request is still null. When the response come it's not triggering onChanges so I can't recalculate this data again, and doCheck triggering "too often".
updateData(componentRecords: ComponentRecord[], importSourceGroup?: ImportSource[], isImportSource = false, component: DiaryNode = null) {
const recordData = [];
const records = isImportSource ? importSourceGroup : componentRecords;
for (const record of records) {
const recordRow: any = record.ID === 'addingRow' ? record : {
ID: record.ID,
InputTypeID: record.InputTypeID,
SubRecords: record.SubRecords,
attachmentField: record.Fields ? record.Fields.find(({Type}) => Type === DiaryFieldType.ATTACHMENT) : null,
documentsFolder: null,
DateUpdated: null,
ComponentInstanceID: null,
linkedUnits: {},
recordRef: record
};
if (record.ID !== 'addingRow') {
if (isImportSource) {
recordRow.DateUpdated = (record as ImportSource).DateUpdated;
recordRow.ComponentInstanceID = (record as ImportSource).ComponentInstanceID;
}
if (recordRow.attachmentField && recordRow.attachmentField.Value) {
this.subManager.add(this.documentsApiService
.getFileDetailsByFolderID(recordRow.attachmentField.Value)
.subscribe((documents: DocumentsFolder) =>
recordRow.documentsFolder = documents));
}
if (record.Fields) {
for (const field of record.Fields) {
const label = field.Label;
recordRow[label] = field.Type === DiaryFieldType.INTEGER ? parseInt(field.Value, 10) : field.Value;
const schema = component && component.Schema && component.Schema.find(diaryFormField => diaryFormField.Label === label);
if (schema && schema.LinkedUnit) {
recordRow.linkedUnits[label] = schema.LinkedUnit.Attributes.List.PickListItems[0].Label;
}
}
}
}
recordData.push(recordRow);
}
return recordData;
}
The part that is async is
if (recordRow.attachmentField && recordRow.attachmentField.Value) {
this.subManager.add(this.documentsApiService
.getFileDetailsByFolderID(recordRow.attachmentField.Value)
.subscribe((documents: DocumentsFolder) =>
recordRow.documentsFolder = documents));
}
So I don't know what is the best solution for this but I was wondering if it's possible to wait here for the response, and go furthure when it comes.
What do you think?
In short the property cannot be assigned synchronously using the asynchronous HTTP request. Instead you need to make entire paradigm asynchronous. Then you could subscribe to the function updateData() to fetch the array.
Additionally you could use RxJS forkJoin function to combine multiple parallel observables. Try the following
updateData(
componentRecords: ComponentRecord[],
importSourceGroup?: ImportSource[],
isImportSource = false,
component: DiaryNode = null
): Observable<any> { // <-- return `Observable` here
const records = isImportSource ? importSourceGroup : componentRecords;
return forkJoin( // <-- use `forkJoin` to combine multiple parallel observables
records.map(record => {
const recordRow: any = record.ID === 'addingRow' ? record : {
ID: record.ID,
InputTypeID: record.InputTypeID,
SubRecords: record.SubRecords,
attachmentField: record.Fields ? record.Fields.find(({Type}) => Type === DiaryFieldType.ATTACHMENT) : null,
documentsFolder: null,
DateUpdated: null,
ComponentInstanceID: null,
linkedUnits: {},
recordRef: record
};
if (record.ID !== 'addingRow') {
if (isImportSource) {
recordRow.DateUpdated = (record as ImportSource).DateUpdated;
recordRow.ComponentInstanceID = (record as ImportSource).ComponentInstanceID;
}
if (record.Fields) {
for (const field of record.Fields) {
const label = field.Label;
recordRow[label] = field.Type === DiaryFieldType.INTEGER ? parseInt(field.Value, 10) : field.Value;
const schema = component && component.Schema && component.Schema.find(diaryFormField => diaryFormField.Label === label);
if (schema && schema.LinkedUnit) {
recordRow.linkedUnits[label] = schema.LinkedUnit.Attributes.List.PickListItems[0].Label;
}
}
}
if (recordRow.attachmentField && recordRow.attachmentField.Value) {
return this.documentsApiService.getFileDetailsByFolderID(recordRow.attachmentField.Value).pipe( // <-- return the HTTP request
map((documents: DocumentsFolder) => ({ ...recordRow, recordRow.documentsFolder: documents })) // <-- spread operator to append new value to object
);
}
return of(recordRow); // <-- use `of()` to return as observable
}
return of(recordRow); // <-- use `of()` to return as observable
})
);
}
See here to learn more about fetching info from async request.

How do I avoid infinite loop when using Redux state in useEffect dependency array?

I'm trying to figure out why my useEffect function ends up in an infinite loop.
I have two variables that are hooked into my Redux store:
const vehicles: AllVehiclesCollection = useSelector((state: ReduxState) => state.claims?.vehicles ?? {});
const properties: AllPropertiesCollection = useSelector((state: ReduxState) => state.claims?.properties ?? {});
and I have an action that is dispatched to the store that updates these only after a user clicks a button.
I have a useEffect that will trigger based on either of these variables changing.
useEffect(() => {
let fullVehicleList: DropdownData[] = getFormattedVehicleListForDisplay();
let fullPropertyList: DropdownData[] = getFormattedPropertyListForDisplay();
let fullList = fullVehicleList.concat(fullPropertyList);
if (fullList.length > 0) {
setVehiclesAndPropertiesList(fullList);
} else {
setVehiclesAndPropertiesList(null);
}
}, [vehicles, properties]);
Nowhere in this code are the vehicles or properties variables changed or any actions dispatched that would change the Redux state.
getFormattedVehicleListForDisplay function:
const getFormattedVehicleListForDisplay = () => {
let list: DropdownData[] = [];
if (Object.keys(vehicles).length > 0) {
let thisPolicysVehicles = [];
if (vehicles !== null) {
const key = `${selectedPolicy.symbol}${selectedPolicy.number}`;
thisPolicysVehicles = vehicles[key];
}
if (thisPolicysVehicles && thisPolicysVehicles.length > 0) {
thisPolicysVehicles.forEach((vehicle: VehicleInformation) => {
if (vehicle.vehicleMake !== OTHER_VEHICLE) {
list.push({
label: formatVehicleForDisplay(vehicle),
value: { ...vehicle, type: 'V' },
});
} else {
list.push({ label: vehicle.vehicleMake, value: {} });
}
});
}
}
return list;
};
getFormattedPropertyListForDisplay function:
const getFormattedPropertyListForDisplay = () => {
let list: DropdownDataOMIG[] = [];
if (Object.keys(properties).length > 0) {
let thisPolicysProperties = [];
if (properties !== null) {
const key = `${selectedPolicy.symbol}${selectedPolicy.number}`;
thisPolicysProperties = properties[key];
}
if (thisPolicysProperties && thisPolicysProperties.length > 0) {
thisPolicysProperties.forEach((property: LocationInformation) => {
if (property.locStreet1 !== OTHER_PROP) {
list.push({
label: formatPropertyForDisplay(property),
value: { ...property, type: 'P' },
});
} else {
list.push({ label: property.locStreet1, value: {} });
}
});
}
}
return list;
};
For reference, the data in vehicles and properties is a set of key-value pairs where the key is a unique identifier of a given account number and the value is an array of vehicle/property objects for that account.
Any idea why this goes into an infinite loop when using Redux state in the dependency array? Is there a different way to use Redux state in a dependency array? Thanks!
When using
const vehicles = useSelector((state: ReduxState) => state.claims?.vehicles ?? {});
Each time this is triggered, and you don't have vehicles in your store, you return a new object {}. and {} === {} // false
So ain your useEffect dependency array, it's each time a new Object, so useEffect is triggered.
So either remove your || {} in your selector (because null === null & undefined === undefined) or consider moving to useShallowSelector as explained in react-redux documentation

ES2015 + flow : self-referenced (circular ?) enums

Using rollup, buble, flow-remove-types,
Is it possible to create an ENUM of classes instances for chess board representation, as types, like this:
// a Ref is a class or a type
class Ref { /* ... */ }
// Refs is an ENUM
Refs.forEach((ref: Ref, key: string) => {
console.log(key) // outputs: "a1", ..., "h8" successively
})
// type checking should work
typeof Refs.a1 === Ref // true
// etc...
typeof Refs.h8 === Ref // true
// move(ref) --> ref will work
Refs.a1.move(7, 7) === Refs.h8 // true
Refs.h8.move(-7, -7) === Refs.h8 // true
// with...
Refs.a1.move(0, 0) === Refs.a1 // true
// void reference
Refs.a1.move(-1, -1) === null
// or
Refs.a1.move(-1, -1) === Refs.EMPTY
A possible modular implementation would be packing the Ref class and the Refs collection in the same file, with a initialization code, like Enuify lib does... But how to make the Ref#move method working properly ??
The same as :
TicTacToe.X.us =TicTacToe.X
TicTacToe.X.them =TicTacToe.O
TicTacToe.O.us =TicTacToe.O
TicTacToe.O.them =TicTacToe.X
something like this, is perfectible, but works fine for me...
type TF = 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'
type TR = '1'|'2'|'3'|'4'|'5'|'6'|'7'|'7'
type TRefDef = {
file: TF,
fidx: number,
rank: TR,
ridx: number
}
interface IRef {
move (df: number, dr: number) : IRef
}
const FILES: Array <TF> = 'abcdefgh'.split('')
const RANKS: Array <TR> = '12345678'.split('')
const all: {
[key:string] : IRef
} = {}
const compute = function(fidx: number, ridx: number): IRef {
const file: TF = FILES[fidx]
const rank: TR = RANKS[ridx]
return all[file + rank]
}
const select = function(key: string) : IRef {
return all[key]
}
const get = function(arg1: string | number, arg2: ?number) : IRef {
if(arguments.length === 1) {
return select (arg1)
}
if(arguments.length === 2) {
return compute (arg1, arg2)
}
}
const each = function (callback) {
Object.keys(all).forEach((key, idx) => {
callback.call(this, all[key], idx)
})
}
class Ref implements IRef {
constructor (refdef: TRefDef) {
this.file = refdef.file
this.fidx = refdef.fidx
this.rank = refdef.rank
this.ridx = refdef.ridx
this.key = this.file + this.rank
}
toString() : string {
return 'Ref: ' + '(' + this.fidx + ',' + this.ridx + ')' + ' ' + this.file + this.rank
}
move (df: number, dr: number) : Ref {
let f = FILES.indexOf(fidx)
let r = RANKS.indexOf(ridx)
f += df
r += dr
return all[FILES[f] + RANKS[r]]
}
}
FILES.forEach((file, fidx) => {
RANKS.forEach( (rank, ridx) => {
const key: string = file + rank
const ref: Ref = new Ref({ file, fidx, rank, ridx })
all[key] = ref
})
})
Ref.empty = new Ref('', -1, '', -1)
const Refs = { compute, select, each, get }
// let f = { compute, each, selection }
// console.log(f)
// export { compute, each, select, Ref }
export { Refs, Ref }

Categories