Component array property call by reference in angular 8 - javascript

I have a method in an angular component that pass component property array in method, but property value is not changing:
private districtModel : LocationModel[] = [];
valueChange(apiService : ApiService,control : FormControl,url,targetModel: LocationModel[]) {
control.valueChanges.subscribe(newValue=>{
if(control.value === ""){
console.log("empty");
console.log(this.stateTemp);
this.stateModel = this.stateTemp;
}
else{
this.stateModel = this.filterValues(newValue,this.stateModel);
}
if(this.stateModel.length===1){
console.log(this.stateModel[0].id);
apiService.GetLocationById<LocationModel[]>(url,this.stateModel[0].id)
.subscribe(data=> {
targetModel = data;
//console.log(this.districtModel);
});
}
});
}
Function calling
this.valueChange(apiService,this.societyForm.controls['State'] as
FormControl,"https://localhost:44355/Location/GetDistrict?StateId=",this.districtModel);
I want to change the value that is this.districtModel inside function but it's not changing

Here
targetModel = data;
you assign data to the local variable targetModel which will not change this.districtModel. What you could do, is clear the original array and assign new values to it:
targetModel.splice(0, targetModel.length, ...data);
This removes targetModel.length items from the array beginning at 0 and adds all items from data.

If your object LocationModel contains nested objects, you can deep copy your array with lodash, before passing it to the function:
import * as _ from "lodash";
// ...
const clonedDistrictModel = _.cloneDeep(districtModel);
If you only need shallow copy, use the spread operator like that:
const clonedDistrictModel = { ...districtModel }
Take a look here if you want to know different types of copy: https://www.freecodecamp.org/news/copying-stuff-in-javascript-how-to-differentiate-between-deep-and-shallow-copies-b6d8c1ef09cd/

You can deep copy object to another object the following code.
JSON.parse(JSON.stringify(Object))

Related

Reduce callback modying wrong array

So I'm trying to create a copy of my state dataMonth every time it changes, that copy is dataYear. I used the spread operator to create a brand new object with only the content copied.
I have to declare dataYear outside the useEffect since I want to use it later on in the global scope.
const [dataMonth, setDataMonth] = useState(null);
var dataYear = {};
I used reduce on my property 'data' to sum up the 12 months worth of value.
useEffect(() => {
if (dataMonth !== null) {
dataYear = { ...dataMonth };
dataYear.labels = ["Année " + year];
dataYear.datasets.forEach((value) => {
value.data.reduce((prev, val) => {
return value.data = prev + val;
});
value.data = [value.data];
});
}
}, [dataMonth]);
The 'labels' property is changed only in the dataYear object as expected, but the 'data' property that was modified inside the reduce callback changed dataYear AND dataMonth.
How does dataMonth get modified when I'm not referencing it ?
Thanks to #OluwafemiSule for pointing out that dataMonth was indeed being referenced.
The spread syntax ... was doing a shallow copy of dataMonth, meaning that my nested objects were still referencing dataMonth.
To create new references for nested objects, I used structuredClone which deep clones my object, thus preventing any mutation of the source object.

Define custom array-like object with numeric access

How can I define a collection object in JavaScript that behaves like an array in that it provides access to its items through the numeric index operator? I'd like to allow this code:
// Create new object, already done
let collection = new MyCollection();
// Add items, already done
collection.append("a");
collection.append("b");
// Get number of items, already done
console.log(collection.length); // -> 2
// Access first item - how to implement?
console.log(collection[0]); // -> "a"
My collection class looks like this (only part of the code shown):
function MyCollection(array) {
// Create new array if none was passed
if (!array)
array = [];
this.array = array;
}
// Define iterator to support for/of loops over the array
MyCollection.prototype[Symbol.iterator] = function () {
const array = this.array;
let position = -1;
let isDone = false;
return {
next: () => {
position++;
if (position >= array.length)
isDone = true;
return { done: isDone, value: array[position] };
}
};
};
// Gets the number of items in the array.
Object.defineProperty(MyCollection.prototype, "length", {
get: function () {
return this.array.length;
}
});
// Adds an item to the end of the array.
MyCollection.prototype.append = function (item) {
this.array.push(item);
};
I think jQuery supports this index access, for example, but I can't find the relevant part in their code.
Your class can extend Array: class MyCollection extends Array {...} - you no longer need the internal array property, the iterator, or the custom length property, and you can define arbitrary methods like append which would simply call this.push(item);.
Edit: if you can't use the class keyword, you can do it the old school way:
// note - this is no different than doing class MyCollection extends Array {}
function MyCollection() { }
MyCollection.prototype = new Array();
MyCollection.prototype.constructor = MyCollection;
Or you can use JavaScript Proxies so that any time someone tries to read a property you can return the value they're looking for. This shouldn't be "slow" - though it's unavoidably slower than plain objects.
Edit: If you really want to make life difficult for yourself and everybody else using your code, you can update your append function to do the following:
MyCollection.prototype.append = function (item) {
this.array.push(item);
this[this.array.length - 1] = item;
};

How to create an object from constructor data at class initialisation

I want it to be the case that when I instantiate an instance of the class TableCategory, an object is created using the data passed in at instantiation. I want this to happen automatically at instantiation. I don't want have to instantiate the class and then call a method that creates the object. This seems unnecessary cumbersome.
I am using a class because I then want to manipulate the resultant object using getters and setters for multiple instantiations. (In case you wonder why I'm not just using an object in the first place.)
I'm not 100% clear on classes in JS so I'm not sure where I'm going wrong. Please note the object creation is a the product of a function it takes an array passed in at instantiation and an array that is native to the class.
Here's my class:
export class TableCategory {
constructor(categoryValues = []) {
this.categoryValues = categoryValues;
this.categoryKeys = ['alpha','beta','gamma', 'delta'];
this.categoryData = this.categoryKeys.forEach(function(key, i) {
return this.categoryData[key] = this.categoryValues[i];
});
}
}
Then, for example:
const foo = new TableCategory(['a'. 'b', 'c', 'd']);
console.log(foo.categoryData.beta); // b
Perhaps I need to use static ? Not sure, grateful for any help
forEach() doesn't return anything. Create an empty categoryData object, and then fill it in in the forEach loop.
Also, you need to use an arrow function to be able to access this in the callback function.
class TableCategory {
constructor(categoryValues = []) {
this.categoryValues = categoryValues;
this.categoryKeys = ['alpha', 'beta', 'gamma', 'delta'];
this.categoryData = {};
this.categoryKeys.forEach((key, i) =>
this.categoryData[key] = this.categoryValues[i]
)
}
}
const foo = new TableCategory(['a', 'b', 'c', 'd']);
console.log(foo.categoryData.beta); // b
forEach does not return anything else than undefined. You can still get the desired object in a functional way, but with Object.fromEntries.
Also, as you say you use a class because you intend to mutate the instance(s) with getters and setters, I don't think it is a good idea to still store the values and keys separately from the third property, which has all key/value information.
You could for instance do this:
class TableCategory {
constructor(values = []) {
this._obj = Object.fromEntries(['alpha','beta','gamma', 'delta']
.map((key, i) => [key, values[i]]));
}
get values() {
return Object.values(this._obj);
}
get keys() {
return Object.keys(this._obj);
}
}
let obj = new TableCategory(["this", "is", "a", "test"]);
console.log(obj.values);

Object.assign() and Spread properties still mutating original

I'm trying to assign the value of an array element to an object. After first attempting something like, e.g.bar = foo[0]; I've discovered that any change to bar also changes foo[0], due to having the same reference.
Awesome, thought no one, and upon reading up on immutability and the ES6 Object.assign() method and spread properties, I thought it would fix the issue. However, in this case it doesn't. What am I missing?
EDIT: Sorry about the accountTypes confusion, I fixed the example.
Also, I would like to keep the class structure of Settings, so let copy = JSON.parse(JSON.stringify(original)); is not really what I'm after in this case.
//this object will change according to a selection
currentPreset;
//this should remain unchanged
presets: {name: string, settings: Settings}[] = [];
ngOnInit()
{
this.currentPreset = {
name: '',
settings: new Settings()
}
this.presets.push({name: 'Preset1', settings: new Settings({
settingOne: 'foo',
settingTwo: false,
settingThree: 14
})
});
}
/**
* Select an item from the `presets` array and assign it,
* by value(not reference), to `currentPreset`.
*
* #Usage In an HTML form, a <select> element's `change` event calls
* this method to fill the form's controls with the values of a
* selected item from the `presets` array. Subsequent calls to this
* method should not affect the value of the `presets` array.
*
* #param value - Expects a numerical index or the string 'new'
*/
setPreset(value)
{
if(value == 'new')
{
this.currentPreset.name = '';
this.currentPreset.settings.reset();
}
else
{
this.currentPreset = {...this.presets[value]};
//same as above
//this.currentPreset = Object.assign({}, this.presets[value]);
}
}
Try this : let copy = original.map(item => Object.assign({}, ...item));
This will create a new object without any reference to the old object original
In case if you want to do this for an array try the same with []
let copy = original.map(item => Object.assign([], ...item));
You have to do a deep copy, this the easiest way:
let copy = JSON.parse(JSON.stringify(original));
This doesn't really answer the question, but since the object I'm trying not to mutate doesn't have nested properties within, I call the assignment at the property level and the shallow copy here is fine.
setPreset(value)
{
if(value == 'new')
{
this.currentPreset.name = '';
this.currentPreset.settings.reset();
}
else
{
this.currentPreset.name = this.presets[value].name;
this.currentPreset.privileges = Object.assign(new Settings(),
this.presets[value].settings);
}
}
A better solution, since I'm creating a new Settings() anyway, might be to move this logic to a Settings class method and call it in the constructor
I had the same problem recently, and I could not figure out why some of my objects were changing their properties. I had to change my code to avoid mutation. Some of the answers here helped me understand afterwards, such as this great article : https://alistapart.com/article/why-mutation-can-be-scary/
I recommend it. The author gives a lot of examples and useful libraries that can outperform Object.assign() when it comes to embedded properties.

How can I store reference to a variable within an array?

I'm trying to create an array that maps strings to variables. It seems that the array stores the current value of the variable instead of storing a reference to the variable.
var name = "foo";
var array = [];
array["reference"] = name;
name = "bar";
// Still returns "foo" when I'd like it to return "bar."
array["reference"];
Is there a way to make the array refer to the variable?
Put an object into the array instead:
var name = {};
name.title = "foo";
var array = [];
array["reference"] = name;
name.title = "bar";
// now returns "bar"
array["reference"].title;
You can't.
JavaScript always pass by value. And everything is an object; var stores the pointer, hence it's pass by pointer's value.
If your name = "bar" is supposed to be inside a function, you'll need to pass in the whole array instead. The function will then need to change it using array["reference"] = "bar".
Btw, [] is an array literal. {} is an object literal.
That array["reference"] works because an Array is also an object, but array is meant to be accessed by 0-based index. You probably want to use {} instead.
And foo["bar"] is equivalent to foo.bar. The longer syntax is more useful if the key can be dynamic, e.g., foo[bar], not at all the same with foo.bar (or if you want to use a minimizer like Google's Closure Compiler).
Try pushing an object to the array instead and altering values within it.
var ar = [];
var obj = {value: 10};
ar[ar.length] = obj;
obj.value = 12;
alert(ar[0].value);
My solution to saving a reference is to pass a function instead:
If the variable you want to reference is called myTarget, then use:
myRef = function (newVal) {
if (newVal != undefined) myTarget = newVal;
return myTarget;
}
To read the value, use myRef();. To set the value, use myRef(<the value you want to set>);.
Helpfully, you can also assign this to an array element as well:
var myArray = [myRef];
Then use myArray[0]() to read and myArray[0](<new value>) to write.
Disclaimer: I've only tested this with a numerical target as that is my use case.
My solution to saving a reference is to pass a function instead:
If the variable you want to reference is called 'myTarget', then use:
myRef = function (newVal) {
if (newVal != undefined)
myTarget = newVal;
return myTarget;
}
To read the value, use myRef();. To set the value, use myRef(value_to_set);.
Helpfully, you can also assign this to an array element as well:
var myArray = [myRef];
Then use myArray0 to read and myArray[0](value_to_set) to write.
Disclaimer: I've only tested this with a numerical target as that is my use case.

Categories