I have array of two objects:
genericItems: Item[] = [];
backupData: Item[] = [];
I am populating my HTML table with genericItemsdata. The table is modifiable. There is a reset button to undo all changes done with backUpData. This array is populated by a service:
getGenericItems(selected: Item) {
this.itemService.getGenericItems(selected).subscribe(
result => {
this.genericItems = result;
});
this.backupData = this.genericItems.slice();
}
My idea was that, the user changes will get reflected in first array and second array can be used as backup for reset operation. The issue I am facing here is when the user modifies the table (genericItems[]) the second array backupData also gets modified.
How is this happening and how to prevent this?
Clone an object:
const myClonedObject = Object.assign({}, myObject);
Clone an Array:
Option 1 if you have an array of primitive types:
const myClonedArray = Object.assign([], myArray);
Option 2 - if you have an array of objects:
const myArray= [{ a: 'a', b: 'b' }, { a: 'c', b: 'd' }];
const myClonedArray = [];
myArray.forEach(val => myClonedArray.push(Object.assign({}, val)));
Cloning Arrays and Objects in javascript have a different syntax. Sooner or later everyone learns the difference the hard way and end up here.
In Typescript and ES6 you can use the spread operator for array and object:
const myClonedArray = [...myArray]; // This is ok for [1,2,'test','bla']
// But wont work for [{a:1}, {b:2}].
// A bug will occur when you
// modify the clone and you expect the
// original not to be modified.
// The solution is to do a deep copy
// when you are cloning an array of objects.
To do a deep copy of an object you need an external library:
import {cloneDeep} from 'lodash';
const myClonedArray = cloneDeep(myArray); // This works for [{a:1}, {b:2}]
The spread operator works on object as well but it will only do a shallow copy (first layer of children)
const myShallowClonedObject = {...myObject}; // Will do a shallow copy
// and cause you an un expected bug.
To do a deep copy of an object you need an external library:
import {cloneDeep} from 'lodash';
const deeplyClonedObject = cloneDeep(myObject); // This works for [{a:{b:2}}]
Using map or other similar solution do not help to clone deeply an array of object.
An easier way to do this without adding a new library is using JSON.stringfy and then JSON.parse.
In your case this should work :
this.backupData = JSON.parse(JSON.stringify(genericItems));
When using the last version of JS/TS, installing a large library like lodash for just one/two function is a bad move. You will heart your app performance and in the long run you will have to maintain the library upgrades! check https://bundlephobia.com/result?p=lodash#4.17.15
For small objet lodash cloneDeep can be faster but for larger/deeper object json clone become faster. So in this cases you should not hesitate to use it. check https://www.measurethat.net/Benchmarks/Show/6039/0/lodash-clonedeep-vs-json-clone-larger-object and for infos https://v8.dev/blog/cost-of-javascript-2019#json
The inconvenient is that your source object must be convertible to JSON.
try the following code:
this.cloneArray= [...this.OriginalArray]
The following line in your code creates a new array, copies all object references from genericItems into that new array, and assigns it to backupData:
this.backupData = this.genericItems.slice();
So while backupData and genericItems are different arrays, they contain the same exact object references.
You could bring in a library to do deep copying for you (as #LatinWarrior mentioned).
But if Item is not too complex, maybe you can add a clone method to it to deep clone the object yourself:
class Item {
somePrimitiveType: string;
someRefType: any = { someProperty: 0 };
clone(): Item {
let clone = new Item();
// Assignment will copy primitive types
clone.somePrimitiveType = this.somePrimitiveType;
// Explicitly deep copy the reference types
clone.someRefType = {
someProperty: this.someRefType.someProperty
};
return clone;
}
}
Then call clone() on each item:
this.backupData = this.genericItems.map(item => item.clone());
Array copy explained - Deep & Shallow
Below code might help you to copy the first level objects
let original = [{ a: 1 }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))
so for below case, values remains intact
copy[0].a = 23
console.log(original[0].a) //logs 1 -- value didn't change voila :)
Fails for this case
let original = [{ a: {b:2} }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))
copy[0].a.b = 23;
console.log(original[0].a) //logs 23 -- lost the original one :(
Try lodash separate ES module - cloneDeep:
I would say go for lodash cloneDeep API ( This can be
installed as a separate module, reduced code footprint for treeshaking ) which helps you to copy the objects
inside objects completely dereferencing from original one's.
As another option you can rely on JSON.stringify & JSON.parse
methods to dereference quickly and performant too.
Refer documentation: https://github.com/lodash/lodash
Individual Package : https://www.npmjs.com/package/lodash.clonedeep
you can use map function
toArray= fromArray.map(x => x);
Clone an object / array (without reference) in a very powerful way
You can get deep-copy of your object / array using #angular-devkit.
import { deepCopy } from '#angular-devkit/core/src/utils/object';
export class AppComponent {
object = { .. some object data .. }
array = [ .. some list data .. ]
constructor() {
const newObject = deepCopy(this.object);
const newArray = deepCopy(this.array);
}
}
I have the same issue with primeNg DataTable. After trying and crying, I've fixed the issue by using this code.
private deepArrayCopy(arr: SelectItem[]): SelectItem[] {
const result: SelectItem[] = [];
if (!arr) {
return result;
}
const arrayLength = arr.length;
for (let i = 0; i <= arrayLength; i++) {
const item = arr[i];
if (item) {
result.push({ label: item.label, value: item.value });
}
}
return result;
}
For initializing backup value
backupData = this.deepArrayCopy(genericItems);
For resetting changes
genericItems = this.deepArrayCopy(backupData);
The magic bullet is to recreate items by using {} instead of calling constructor.
I've tried new SelectItem(item.label, item.value) which doesn't work.
the easiest way to clone an array is
backUpData = genericItems.concat();
This will create a new memory for the array indexes
If your items in the array are not primitive you can use spread operator to do that.
this.plansCopy = this.plans.map(obj => ({...obj}));
Complete answer : https://stackoverflow.com/a/47776875/5775048
Try this:
[https://lodash.com/docs/4.17.4#clone][1]
var objects = [{ 'a': 1 }, { 'b': 2 }];
var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true
It looks like you may have made a mistake as to where you are doing the copy of an Array. Have a look at my explanation below and a slight modification to the code which should work in helping you reset the data to its previous state.
In your example i can see the following taking place:
you are doing a request to get generic items
after you get the data you set the results to the this.genericItems
directly after that you set the backupData as the result
Am i right in thinking you don't want the 3rd point to happen in that order?
Would this be better:
you do the data request
make a backup copy of what is current in this.genericItems
then set genericItems as the result of your request
Try this:
getGenericItems(selected: Item) {
this.itemService.getGenericItems(selected).subscribe(
result => {
// make a backup before you change the genericItems
this.backupData = this.genericItems.slice();
// now update genericItems with the results from your request
this.genericItems = result;
});
}
Looks like what you want is Deep Copy of the object. Why not use Object.assign()? No libaries needed, and its a one-liner :)
getGenericItems(selected: Item) {
this.itemService.getGenericItems(selected).subscribe(
result => {
this.genericItems = result;
this.backupDate = Object.assign({}, result);
//this.backupdate WILL NOT share the same memory locations as this.genericItems
//modifying this.genericItems WILL NOT modify this.backupdate
});
}
More on Object.assign(): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
array level cloning solutions didn't work for me, added this clone utility in typescript which does a deeper clone that worked in my case
export function cloneArray<T>(arr: T[]): T[] {
return arr.map((x) => Object.assign({}, x))
}
by "didn't work" i meant, that if i passed an array to a function [...arr] or Object.assign([], arr) or arr.splice() it was still mutating the original array
Try this
const returnedTarget = Object.assign(target, source);
and pass empty array to target
in case complex objects this way works for me
$.extend(true, [], originalArray) in case of array
$.extend(true, {}, originalObject) in case of object
How can I get data back as an array or generic list from a Firebase database in Unity3D without knowing ahead of time what the name (key) of the children are?
I have been trying out the new Unity Firebase plugin, and I am having an issue figuring out how to get all the children in a specific location, and put the names (the key) and the values into arrays or generic lists so that I can work on the data locally. Forgive me for being so new to Firebase and probably using bad techniques to do this, and this plugin being so new its pretty hard for me to get much outside help, as there are not a lot of docs and tutorials out there on Firebase Unity.
In this particular case I am trying to create "instant messaging" like functionality, without the use of Firebase messaging, and just using regular Firebase database stuff instead. It might have been easier to use Firebase messaging, but mostly for the sake of learning and customization I want to do this on my own with just the Firebase database.
I insert data into the database like this:
public void SendMessage(string toUser, string msg)
{
Debug.Log(String.Format("Attempting to send message from {0} to {1}", username, toUser));
DatabaseReference reference = FirebaseDatabase.DefaultInstance.GetReference("Msgs");
string date = Magnet.M.GetCurrentDate();
// send data to the DB
reference.Child(toUser).Child(username).Child(date).SetValueAsync(msg);
// user receiving message / user sending message > VALUE = "hello dude|20170119111325"
UpdateUsers();
}
And then I try and get it back like this:
public string[] GetConversation(string userA, string userB)
{
// get a conversation between two users
string[] convo = new string[0];
FirebaseDatabase.DefaultInstance.GetReference("Msgs").GetValueAsync().ContinueWith(task =>
{
Debug.Log("Getting Conversation...");
if (task.IsFaulted || task.IsCanceled)
{
Debug.LogError("ERROR: Task error in GetConversation(): " + task.Exception);
}
else if (task.IsCompleted)
{
DataSnapshot snapshot = task.Result;
string[] messagesA = new string[0], messagesB = new string[0];
if(snapshot.HasChild(userA))
{
// userA has a record of a conversation with other users
if(snapshot.Child(userA).HasChild(userB)) // userB has sent messages to userA before
{
Debug.Log("Found childA");
long count = snapshot.Child(userA).Child(userB).ChildrenCount;
messagesA = new string[count];
var kids = snapshot.Child(userA).Child(userB).Children;
Debug.Log(kids);
for (int i = 0; i < count; i++)
{
// this won't work, but is how I would like to access the data
messagesA[i] = kids[i].Value.ToString(); // AGAIN.... will not work...
}
}
}
if(snapshot.HasChild(userB))
{
if(snapshot.Child(userB).HasChild(userA)) // userA sent a message to userB before
{
Debug.Log("Found childB");
long count = snapshot.Child(userB).Child(userA).ChildrenCount;
messagesA = new string[count];
var kids = snapshot.Child(userB).Child(userA).Children;
Debug.Log(kids);
// messy incomplete testing code...
}
}
// HERE I WOULD ASSIGN ALL THE MESSAGES BETWEEN A AND B AS 'convo'...
}
Debug.Log("Done Getting Conversation.");
});
return convo;
}
But obviously this won't work, because DataSnapshot won't let me access it like an array or generic list using indices, and I can't figure out how to treat the data when I don't know the names (the keys) of all the children, and just want to get them out one by one in any order... and since they are named by the date/time they are entered into the DB, I won't know ahead of time what the childrens names (keys) are, and I can't just say "GetChild("20170101010101")" because that number is generated when its sent to the DB from any client.
FYI here is what the DB looks like:
Figured out the answer to your question. Here's my code snippet. Hope this would help!
void InitializeFirebase() {
FirebaseApp app = FirebaseApp.DefaultInstance;
app.SetEditorDatabaseUrl ("https://slol.firebaseio.com/");
FirebaseDatabase.DefaultInstance
.GetReference ("Products").OrderByChild ("category").EqualTo("livingroom")
.ValueChanged += (object sender2, ValueChangedEventArgs e2) => {
if (e2.DatabaseError != null) {
Debug.LogError (e2.DatabaseError.Message);
}
if (e2.Snapshot != null && e2.Snapshot.ChildrenCount > 0) {
foreach (var childSnapshot in e2.Snapshot.Children) {
var name = childSnapshot.Child ("name").Value.ToString ();
text.text = name.ToString();
Debug.Log(name.ToString());
//text.text = childSnapshot.ToString();
}
}
};
}
Firebase developer here.
Have you tried to use Value at the top level Snapshot? It should return to you an IDictionary where the values can also be lists or nested dictionaries. You will have to use some dynamic inspection to figure out what the values are.
I am learning how to build apps and working with Swift for this project.
I had a buddy help me pull data in from a website and it looks like he created classes with variables and mapped them to certain extensions (IE "Username") so when I call the variable data such as profile I would call it. The below uses luck_30 able to store "Stats.luck_30"
luck_30.text = profile.luck_30
So inside one of my variables that is in this "Profile" class is setup into an array. I can pull the array out of the class, but I can't seem to do for while statement replacing the [#] with a variable from the for command.
func aliveWorkers(profile: Profile) -> NSNumber{
var myworkers : Array = profile.workers!
//this test works and returns the proper value
var testworker: NSNumber = myworkers[0].alive!
println("The satus of the test worker is " + testworker.description)
/* This code is giving error "Could not find member alive" it does not ifor var
for ifor in myworkers{
var thisworker: NSNumber = myworkers[ifor].alive! as NSNumber
}
*/
return 42
}
Your variable ifor is not a counter, it is an actual object. You could do something like this:
for worker in myWorkers {
let workerIsAlive = worker.alive!
}
Alternatively, if you need the index,
for i in 0 ..< myWorkers.count {
let worker = myWorkers[i]
let workerIsAlive = worker.alive!
}
If you need both:
for (i, worker) in enumerate(myWorkers) {
let workerIsAlive = worker.alive!
}
And as a matter of style, I would stay away from NSNumber and use Int or Bool or whatever the data actually is. Also, it looks like the alive variable should not be optional, as you're unwrapping it everywhere. To avoid "mysterious" crashes later, you may want to think about making it a non-optional type.
when using a for in loop, your loop variable isn't an index, its the objects you're looping through. so..
func aliveWorkers() {
var myworkers = [1, 2, 3]
//this test works and returns the proper value
let testworker = myworkers[0]
print("The satus of the test worker is \(testworker)")
for ifor in myworkers {
print(ifor)
}
}
Notice a few things... you don't need to use + to concatenate those strings. you can just use string interpolation. \(variable) inserts the value of variable in the string.
Try to use let instead of var when you don't change the variable. You don't need to explicitly define type on variables either.
i use the following function to retrieve a random person from an array:
func getRandomPerson() -> String{
if(personArray.isEmpty){
return ""
} else {
var tempArray: [String] = []
for person in personArray{
tempArray += [person.getName()]
}
var unsignedArrayCount = UInt32(tempArray.count)
var unsignedRandomNumber = arc4random_uniform(unsignedArrayCount)
var randomNumber = Int(unsignedRandomNumber)
if tempArray.isEmpty {
return ""
} else {
return tempArray[randomNumber]
}
}
}
I would like to use this function inside an array of strings, Like this:
var theDares: [String] = ["Dare1 \(getRandomPerson())", "Dare2", "Dare3", "Dare4", "Dare5"]
But when i use the functions, it only runs the function once. Can you make the function run everytime you use the "Dare1" in this instance.
Thanks in advance
I think you are asking if you can set up your array so every time you fetch the object at index 0, it re-builds the value there.
The short answer is no. Your code is creating an array of strings, and the item at index 0 is built ONCE using a function call.
However, it is possible to make a custom class implement the subscript operator. You could create a custom object that looks like an array and allows you to index into it using an Int index. In response to the index operator you could run custom code that built and returned a random string.
Since it sounds like you're a beginning programmer creating a custom class the implements the subscript operator might be beyond your current abilities however.
Try like this:
let personArray = ["John", "Steve", "Tim"]
var randomPerson: String {
return personArray.isEmpty ? "" : personArray[Int(arc4random_uniform(UInt32(personArray.count)))]
}
println(randomPerson) // "Steve"
I have created an array as shown below
protected function getMyArray(dataArray:Array):Array
{
var labelList:Array=new Array;
for each (var property:Object in dataArray)
{
if (labelList[property.bucketTime] != property.bucketTime)
labelList[property.bucketTime]=property.bucketTime;
}
return labelList;
}
Is it possible to sort the array labelList based on property.bucketTime?
Edit: Sample input dataArray will be like this :
var tempObj:Object = new Object;
tempObj.bucketTime = DateField.stringToDate("30-01-2010", "DD-MM-YYYY").time;
tempObj.score = 76;
dataArray.addItem(tempObj);
tempObj = new Object;
tempObj.bucketTime = DateField.stringToDate("13-02-2010", "DD-MM-YYYY").time;
tempObj.score = 21;
dataArray.addItem(tempObj);
tempObj = new Object;
tempObj.bucketTime = DateField.stringToDate("30-03-2010", "DD-MM-YYYY").time;
tempObj.score = 10;
tempArry.addItem(tempObj);
Unless bucketTime is a number; then you aren't actually populating the array. You're just adding properties to the Array Object, almost like it were a Dictionary. I've seen a Dictionary called Associative Array's and Structures in other languages.
If that is the case, an you're using the Array class as a dictionary, then there is no way to sort it. The very nature of such a structure is that they are not sortable.
However, if property.bucketTime is a number, and you are trying adding items to the array as if they were an array, you can sort using the Array.sort or Array.sortOn methods.
Atlast I sorted the labelList Array.Please find my solution below. Let me know if there is any better way to achieve this.
var termObject:Object = ObjectUtil.getClassInfo(labelList);
var termsArray:Array = termObject.properties;
var sortedColumnArray:Array = new Array;
for each(var term:Object in termsArray){
if(labelList.hasOwnProperty(term)){
sortedColumnArray.push(labelList[term]);
}
}