Push function appears to be overwriting each array element in Angular - arrays

I'm trying to push some conversation messages to an array using the below code:
myConversations: IConversation[] = [];
myConversationMessage: IConversationMessages = {
conversationId: 0,
messageId: 0,
messageText: ''
};
myConversationMessages: IConversationMessages[] = [];
this.conversationService.getConversations().subscribe(conversations => {
this.myConversations = conversations;
for (let i of this.myConversations) {
this.myConversationMessage.conversationId = i.conversationId;
for (let j of i.messages) {
this.myConversationMessage.messageId = j.messageId;
this.myConversationMessage.messageText = j.messageText;
this.myConversationMessages.push(this.myConversationMessage);
}
}
console.log(this.myConversationMessages);
});
I'm retrieving the Conversations & the Messages within them from a JSON object.
Instead of pushing each message to the myConversationMessages array, the following is being outputted in the console:
0
conversationId: 2
messageId:2
messageText: "testing"
1
conversationId: 2
messageId:2
messageText: "testing"
2
conversationId: 2
messageId:2
messageText: "testing"
3
conversationId: 2
messageId:2
messageText: "testing"
So the final "conversation" object is overwriting each array element.
Can someone please tell me why my code is doing this? Thanks a lot in advance
P.S. I can upload further code if it will clear up my issue.

It's because JavaScript objects are passed by reference:
myConversationMessages: IConversationMessages[] = [];
this.conversationService.getConversations().subscribe(conversations => {
this.myConversations = conversations;
for (let i of this.myConversations) {
this.myConversationMessage.conversationId = i.conversationId;
for (let j of i.messages) {
this.myConversationMessage.messageId = j.messageId;
this.myConversationMessage.messageText = j.messageText;
this.myConversationMessages.push(this.myConversationMessage); // <-= Here you are pushing a reference to the same object in every loop. In every loop you are updating the single object, and the reference in each array spot points to the same object
}
}
console.log(this.myConversationMessages);
});
Try this:
for (let i of this.myConversations) {
for (let j of i.messages) {
this.myConversationMessages.push({
conversationId: i.conversationId,
messageId: j.messageId,
messageText: j.messageText
});
}
}
That will make a new object for each iteration

It happened because you added reference object to array. In your example you did not add 3 objects to array, you added three reference of one object three times. And change of properties in one of them causes change in all others.
It should be works:
for (let i of this.myConversations) {
this.myConversationMessage.conversationId = i.conversationId;
for (let j of i.messages) {
let item = new IConversationMessages();
item.messageId = j.messageId;
item.messageText = j.messageText;
this.myConversationMessages.push(item);
}
}

Related

SwiftUI - Return values from array of structs are all in one row

I am calling a function in order to do a select statement in a bundled SQLite database. The function returns an array of structs. The database is being read correctly as I have put some print commands in the code. However the final array only has 1 row in it, which contains all the data, instead of 16 rows of structs.
The struct code, which is in databaseHelper.swift, is...
struct ButtonData: Hashable {
let english: String
let categoryID: Int
let indonesian: String
}
The database code, in databaseHelper, is
class DatabaseHelper {
var buttonVars = [ButtonData]()
var database: Connection!
let buttonsTable = Table("Button")
let english = Expression<String>("english")
let category = Expression<String>("category")
let categoryID = Expression<Int>("ID")
let filename = Expression<String>("filename")
let indonesian = Expression<String>("indonesian")
init() {
do {
let path = Bundle.main.path(forResource: "sga", ofType: "db")!
let database = try Connection(path, readonly: true)
self.database = database
print("Database initialized at path \(path)")
} catch {
print("error")
}
}
func queryDatabase(passedCategory: String) -> [ButtonData] {
do {
let buttons = try self.database.prepare(self.buttonsTable.filter(self.category==passedCategory))
for row in buttons {
print("English: \(row[self.english]), ID: \(row[self.categoryID]), Indonesian: \(row[self.indonesian])")
// buttonVars.append(ButtonData(english: row[english], categoryID: row[categoryID], indonesian: row[indonesian]))
buttonVars.append(ButtonData(english: row[english], categoryID: row[categoryID], indonesian: row[indonesian]))
}
} catch {
print(error)
}
print(buttonVars[0])
print(buttonVars[1])
print(buttonVars[2])
print(buttonVars[3])
print(buttonVars[4])
print(buttonVars[5])
print(buttonVars[6])
print(buttonVars[7])
print(buttonVars[8])
print(buttonVars[9])
print(buttonVars[10])
print(buttonVars[11])
print(buttonVars[12])
print(buttonVars[13])
print(buttonVars[14])
print(buttonVars[15])
return buttonVars
}
}
The function code, which is in SoundPageView.swift (this page calls the database function), is...
func getArrayValues() {
let buttonRows = [DatabaseHelper().queryDatabase(passedCategory: category)]
let btnCount: Int = buttonRows.count
print(btnCount)
print(buttonRows[0])
}
The print values in the console show me that btnCount = 1 but before the array is returned, it is made of 16 rows. It is only after it is returned that it is reduced to 1 row.
Can anyone tell me what I'm doing wrong? I don't know how to access the data. Thanks.
I'm not sure why you are putting the brackets in this call:
let buttonRows = [DatabaseHelper().queryDatabase(passedCategory: category)]
The func queryDatabase returns an array on its own. I think it should be this:
let buttonRows = DatabaseHelper().queryDatabase(passedCategory: category)
Otherwise your result will be an array with one entry, which is the result of the call to queryDatabase.

How to initialize an empty array of object with a predefined array?

I am facing lot of issue to initialize an array of object with a predefined array. I am not being able to copy that array to my new array of objects. If anyone knows it then let me know.
admins is basically an array which contains string items like ["hello","hii","sup",....]
var admins = ["hello","hii","sup"];
var obj = [];
for(var i=0; i<admins.length; i++)
{
obj[i].name = admins[i];
}
console.log(obj);
"TypeError: Cannot set property 'name' of undefined"
Use a map:
var newArray = admins.map((admin) => ({ name: admin }));
IMHO: use spread operator:
const admins = ["John", "Doe", "Duck"];
const obj = [...admins].map(
(admin) => ({ name: admin })
);
console.log(obj);
Try out this
var obj = [];
for (var i = 0; i < admins.length; i++) {
let temp = {};
temp["name"] = admins[i];
obj.push(temp);
}
console.log(obj);
You need to define the obj[i] to empty object (obj[i] = {}).
you are trying to access name property of undefined(obj[i] is undefined in your code).
var obj = [];
for(var i=0; i<admins.length; i++)
{
obj[i] = {
name: admins[i]
}
}
console.log(obj);

How to access array element correctly from JSON in Angular 5 app?

I have the following JSON definition:
export class Company {
name: string;
trips : Trip[] = [];
}
I am able to see the trips in the console using:
console.log(this.company);
But I am not able to access the array elements (trip), I've tried
the following:
for(let i = 0; i < this.company.trips.length; i++){
console.log(this.company.trips[i]);
}
When I display the company to the log I'm getting the trip as you can
see below:
{id: 1}
name: "Sample"
trips: {id: 1, r: "ABC"}
id: 1
Any idea how to access array elements in Angular thanks?
Using a combination of Object.keys() and forEach() will let you iterate through the the object in a similar fashion to an array.
explination
const x = {hello: "hi"};
console.log(Object.keys(x)); // will return array looking like
// [hello] which you can run a for each on.
Object.keys(x).forEach(data => {
console.log(x[data]); // will return `hi`
});
Solution
const trips = {id: 1, r: "ABC"}; // const trips = this.company.trips
if (trips) // check the value exists here
{
Object.keys(trips).forEach((data) => {
console.log(trips[data]);
});
}
if(this.company)
this.Company.trips.forEach(x => {
console.log(x);
});

Typescript: Typescript loop showing undefined

public Update() {
this.Data = this.Items;
console.log(this.Data);
for (let value of this.Data) {
console.log(value);
}
}
console
[Object, Object, Object]
Object
CandidateName:"B"
ControlId:0
CreatedBy:null
CreationDateTime:null
ExamId1:2000
ExamName:" Safety"
Id:1292353
after last object it showing length:3
when i going to loop over this object,it is throwing error length is undefined,please help me.
If I understand correctly, this.Items is probably undefined in some cases and you cannot iterate.
So:
for (let value of (this.Data || [])) {
This guards against bad values
The for in or for of statement should be avoided for iterating over arrays. It has two "drawbacks":
1) The order is not guarantee
2) Also inherited properties will be listed/enumerated, if enumerable:false is not specified when defining the property.
If for example you add a property to your prototype, this loop will iterate also over that one.
Array.prototype.test = "test";
var a = ['a', 'b'];
for (let i in a) {
console.log(a[i]);
}
for (let i of a) {
console.log(i);
}
You should see also your property printed.
If you change your loop into a sequential for loop:
for (let i = 0; i < this.Data.length; i++ value of this.Data) {
console.log(this.Data[i]);
}
or a:
this.Data.forEach((el) => {
console.log(el);
});
you may not see your issue.
If you want to iterate over objects, You must use Object.keys(your_obj). Because object doesn't have length property. You can iterate through only of type 'Array' or 'String' using for of. You can use Object.keys(your_obj).length for sequential for loop for(var i=0; i<Object.keys(your_obj).length; i++)
public Update() {
this.Data = this.Items;
console.log(this.Data);
for (let obj of this.Data) {
console.log(obj);
//iterate over object here
for(let property of Object.keys(obj)){
// write your logic for specific object
}
}
}
Extension to quirimmo's answer, sequential for loop, use this
this.Data.forEach(function(obj){
console.log(obj);
//iterate over object here
for(var i=0; i<Object.keys(obj).length; i++){
// write your logic for specific object
}
})

Changing The value of struct in an array

I want to store structs inside an array, access and change the values of the struct in a for loop.
struct testing {
var value:Int
}
var test1 = testing(value: 6 )
test1.value = 2
// this works with no issue
var test2 = testing(value: 12 )
var testings = [ test1, test2 ]
for test in testings{
test.value = 3
// here I get the error:"Can not assign to 'value' in 'test'"
}
If I change the struct to class it works. Can anyone tell me how I can change the value of the struct.
Besides what said by #MikeS, remember that structs are value types. So in the for loop:
for test in testings {
a copy of an array element is assigned to the test variable. Any change you make on it is restricted to the test variable, without doing any actual change to the array elements. It works for classes because they are reference types, hence the reference and not the value is copied to the test variable.
The proper way to do that is by using a for by index:
for index in 0..<testings.count {
testings[index].value = 15
}
in this case you are accessing (and modifying) the actual struct element and not a copy of it.
Well I am going to update my answer for swift 3 compatibility.
When you are programming many you need to change some values of objects that are inside a collection. In this example we have an array of struct and given a condition we need to change the value of a specific object. This is a very common thing in any development day.
Instead of using an index to determine which object has to be modified I prefer to use an if condition, which IMHO is more common.
import Foundation
struct MyStruct: CustomDebugStringConvertible {
var myValue:Int
var debugDescription: String {
return "struct is \(myValue)"
}
}
let struct1 = MyStruct(myValue: 1)
let struct2 = MyStruct(myValue: 2)
let structArray = [struct1, struct2]
let newStructArray = structArray.map({ (myStruct) -> MyStruct in
// You can check anything like:
if myStruct.myValue == 1 {
var modified = myStruct
modified.myValue = 400
return modified
} else {
return myStruct
}
})
debugPrint(newStructArray)
Notice all the lets, this way of development is safer.
The classes are reference types, it's not needed to make a copy in order to change a value, like it happens with structs. Using the same example with classes:
class MyClass: CustomDebugStringConvertible {
var myValue:Int
init(myValue: Int){
self.myValue = myValue
}
var debugDescription: String {
return "class is \(myValue)"
}
}
let class1 = MyClass(myValue: 1)
let class2 = MyClass(myValue: 2)
let classArray = [class1, class2]
let newClassArray = classArray.map({ (myClass) -> MyClass in
// You can check anything like:
if myClass.myValue == 1 {
myClass.myValue = 400
}
return myClass
})
debugPrint(newClassArray)
To simplify working with value types in arrays you could use following extension (Swift 3):
extension Array {
mutating func modifyForEach(_ body: (_ index: Index, _ element: inout Element) -> ()) {
for index in indices {
modifyElement(atIndex: index) { body(index, &$0) }
}
}
mutating func modifyElement(atIndex index: Index, _ modifyElement: (_ element: inout Element) -> ()) {
var element = self[index]
modifyElement(&element)
self[index] = element
}
}
Example usage:
testings.modifyElement(atIndex: 0) { $0.value = 99 }
testings.modifyForEach { $1.value *= 2 }
testings.modifyForEach { $1.value = $0 }
How to change Array of Structs
for every element:
itemsArray.indices.forEach { itemsArray[$0].someValue = newValue }
for specific element:
itemsArray.indices.filter { itemsArray[$0].propertyToCompare == true }
.forEach { itemsArray[$0].someValue = newValue }
You have enough of good answers. I'll just tackle the question from a more generic angle.
As another example to better understand value types and what it means they get copied:
struct Item {
var value:Int
}
func change (item: Item, with value: Int){
item.value = value // cannot assign to property: 'item' is a 'let' constant
}
That is because item is copied, when it comes in, it is immutable — as a convenience.
Had you made Item a class type then you were able to change its value.
var item2 = item1 // mutable COPY created
item2.value = 10
print(item2.value) // 10
print(item1.value) // 5
This is very tricky answer. I think, You should not do like this:
struct testing {
var value:Int
}
var test1 = testing(value: 6)
var test2 = testing(value: 12)
var ary = [UnsafeMutablePointer<testing>].convertFromArrayLiteral(&test1, &test2)
for p in ary {
p.memory.value = 3
}
if test1.value == test2.value {
println("value: \(test1.value)")
}
For Xcode 6.1, array initialization will be
var ary = [UnsafeMutablePointer<testing>](arrayLiteral: &test1, &test2)
It is possible to use the map function to get this effect - essentially creating a new array
itemsArray = itemsArray.map {
var card = $0
card.isDefault = aCard.token == token
return card
}
I ended up recreating a new array of struct see the example below.
func updateDefaultCreditCard(token: String) {
var updatedArray: [CreditCard] = []
for aCard in self.creditcards {
var card = aCard
card.isDefault = aCard.token == token
updatedArray.append(card)
}
self.creditcards = updatedArray
}
I tried Antonio's answer which seemed quite logical but to my surprise it does not work. Exploring this further I tried the following:
struct testing {
var value:Int
}
var test1 = testing(value: 6 )
var test2 = testing(value: 12 )
var testings = [ test1, test2 ]
var test1b = testings[0]
test1b.value = 13
// I would assume this is same as test1, but it is not test1.value is still 6
// even trying
testings[0].value = 23
// still the value of test1 did not change.
// so I think the only way is to change the whole of test1
test1 = test1b

Resources