Initializing array of objects - Angular - arrays

I have an array of objects and when I try to access to it, I get an error saying:
TypeError: Cannot set property 'ID' of undefined
My code is the following:
export class Carimplements OnInit {
pieces: Piece[] = [];
test(pos){
this.pieces[pos].ID = "test";
}
}
being Piece an object
export class Piece{
ID: string;
doors: string;
}
I call to test(pos) from the HTML with a valid position.
I guess that I am trying to access to the position X of an array that has not been initialized. How could I do it? Is it possible to create a constructor?

Correct syntax for defining array types in TypeScript is this:
pieces: Piece[] = [];
The error is a runtime error. When you run your app you have an empty array pieces (but the variable still initialized with []) but you call test(whatever) which tries to access an array element whatever that doesn't exist.
You can do for example this:
pieces: Piece[] = [{
ID: '1',
doors: 'foo'
}];
and then test this method with test(0).

How about this?
export class Carimplements OnInit {
pieces: Piece[] = [];
test(pos){
this.pieces[pos] = {ID: "test"};
}
}

let pieces: Piece[] = [];
//initialize object before assigning value
test(pos){
this.pieces[pos] = new Piece();
this.pieces[pos].ID = "test";
}

You can try the following method
test(pos){
if(pos < this.pieces.length)
this.pieces[pos].ID = "test";
else
// throw error
}

Related

Easiest way to create a Hash of Arrays (or equivalent) in Typescript?

I am looking to create a Hash of Arrays (or some equivalent structure) that allows me to collect an unknown set of properties (keyed by name) and have each property store an array of things that claimed they have said property.
const currentProperties = currentObject.getProperties();
// we can assume getProperties correctly returns an array of valid properties
currentProperties.forEach( (v) => {
  HoA[ v ].push( currentObject );
});
I want to be able to do something like the above to populate the Hash of Arrays - but how to I actually properly initialize it/do all of the TypeScript stuff? Currently I've been getting by using an enum to manually specify the possible properties that could show up, but I want to adapt it out to a structure that doesn't need to have a property list ahead of time, and can just take whatever shows up as a key.
As noted above, I understand how to solve a version of this problem if I manually specify the expected types of properties to be seen and use a bunch of
if (currentProperties.includes(Properties.exampleOne)) {
this.exampleGroupOne.push(currentObject);
}
but I want to be able to have this work with no prior knowledge of what values of properties exist.
EDIT: some clarification on what I am asking for -
The goal is to have a bunch of objects that have a getProperties() method that returns an array of zero or more attributes. I want to have a data structure that, for each attribute that exists, ends up with an array of the objects that reported that attribute. That is easy when I know the possible attributes ahead of time, but in this case, I won't. For actually acting on the attributes, I'll need a loop that is the attributes on the outer layer [the hash] and the relevant objects on the inner layer [the array]. (This is why I'm assuming HoA)
EDIT #2:
class Alice {
myProps(): string[] {
return ["Apple"];
}
}
class Bob {
myProps(): string[] {
return ["Banana"];
}
}
class Charlie {
myProps(): string[] {
return ["Apple", "Banana"];
}
}
const FruitBasket:{ [prop: string]: string} = {}
const myAlice = new Alice();
const myBob = new Bob();
const myCharlie = new Charlie();
const Objects = [myAlice, myBob, myCharlie];
for (const currentObject of Objects) {
const fruits = currentObject.myProps();
fruits.forEach( (v) => { FruitBasket[v].push(currentObject);});
}
I think this is almost what I want - I am getting an error that push does not exist on type string, but at this point I think I'm just missing something basic because I've been staring at this too long.
EDIT #3:
abstract class JustSomeGuy {
myProps(): string[] {
return [];
}
myName(): string {
return '';
}
}
class Alice extends JustSomeGuy {
myProps(): string[] {
return ["Apple"];
}
myName(): string {
return 'Alice';
}
}
class Bob extends JustSomeGuy {
myProps(): string[] {
return ["Banana"];
}
myName(): string {
return 'Bob';
}
}
class Charlie extends JustSomeGuy {
myProps(): string[] {
return ["Apple", "Banana"];
}
myName(): string {
return 'Charlie';
}
}
const FruitBasket:{ [prop: string]: JustSomeGuy[]} = {}
const myAlice = new Alice();
const myBob = new Bob();
const myCharlie = new Charlie();
const Objects = [myAlice, myBob, myCharlie];
for (const currentObject of Objects) {
const fruits = currentObject.myProps();
fruits.forEach( (v) => { (FruitBasket[v] ??= []).push(currentObject);});
}
for (let key in FruitBasket){
let value = FruitBasket[key];
for (let i = 0; i < value.length; i++){
console.log("In key: " + key + " the ith element [i = " + i + "] is: " + value[i].myName() );
}
}
I believe that this is what I want. Marking this as resolved.
Let's start with the types of the data structures that you described:
type ObjWithProps = {
getProperties (): string[];
};
type PropertyHolders = {
[key: string]: ObjWithProps[] | undefined;
};
// Could also be written using built-in type utilities like this:
// type PropertyHolders = Partial<Record<string, string[]>>;
The type ObjWithProps has a method which returns an array of string elements.
The type PropertyHolders is an object type that is indexed by string values (keys), and each value type is an array of ObjWithProps (if it exists, or undefined if it doesn't) — no object has a value at every possible key.
Next, let's replicate the data structures you showed in your example:
const HoA: PropertyHolders = {};
const currentObject: ObjWithProps = {
getProperties () {
return ['p1', 'p2', 'p3' /* etc. */];
}
};
const currentProperties = currentObject.getProperties();
In the code above, the currentObject has some arbitrary properties (p1, p2, p3). This is just to have reproducible example data. Your own implementation will likely be different, but the types are the same.
Finally, let's look at the part where you assign the values to the hash map:
currentProperties.forEach((v) => {
HoA[v].push(currentObject); /*
~~~~~~
Object is possibly 'undefined'.(2532) */
});
You can see that there's a compiler error where you try to access the array at the key v. Because you aren't sure that the array exists (no object has a value at every key), trying to invoke a push method on undefined would throw a runtime error. TypeScript is trying to help you prevent that case.
Instead, you can use the nullish coalescing assignment operator (??=) to ensure that the array is created (if it doesn't already exist) before pushing in a new value. This is what that refactor would look like:
currentProperties.forEach((v) => {
(HoA[v] ??= []).push(currentObject); // ok
});
Full code in TS Playground
Utility types references:
Record<Keys, Type>
Partial<Type>

Pulling Data from Firebase to a local array, but cannot iterate each item. Length = 0

I have an array and strings defined as such:
Day1;Day2;Day3;Day4;Day5: string;
BurnData: Array<number> = [];
A function as such where I grad the values of their respective days:
grabBurnData(): void{
this.db.collection('MattDataPull').doc('Board1').get().toPromise().then(r => {
this.BurnData.push(
parseInt(r.get('Day1')),
parseInt(r.get('Day2')),
parseInt(r.get('Day3')),
parseInt(r.get('Day4')),
parseInt(r.get('Day5')));
});
}
It seems whyen I console.log, the values are within the array but I cannot iterate over them. I get 'undefined'. What is happening here? How do I fix this issue?
Im assuming that your r.get('Day1') function will return a Number. Try something like this. Change your BurnData Array type to any just in case to avoid any type casting mistakes.
Day1;Day2;Day3;Day4;Day5: string;
BurnData: Array<any> = [];
grabBurnData(): void {
this.db.collection('MattDataPull').doc('Board1').get().toPromise().then(r => {
this.BurnData.push(parseInt(r.get('Day1')));
this.BurnData.push(parseInt(r.get('Day2')));
this.BurnData.push(parseInt(r.get('Day3')));
this.BurnData.push(parseInt(r.get('Day4')));
this.BurnData.push(parseInt(r.get('Day5')));
});
}

ReactJs - TypeError: Cannot assign to read only property 'name' of object '#<Object>'

I have an array of objects:
var subcategories = [{name:'gloves', tag:'wool'}, {name:'boots', tag: 'leather'}]
All I want to do is to find the index of the object to change a name or a tag. I use this function:
function setSubcat(val, index, lang){
var newArr = []
var obj = {
'name': val
}
subcategories.map((val, i)=>{
if(index === i){
var newObj = Object.assign(val, obj)
newArr.push(newObj)
}
newArr.push(val)
})
setSubcategories(newArr)
}
The error happens at var newObj = Object.assign(val, obj)
I thought the error means I can't mutate the state directly and so I have to make a copy. I thought that mapping through subcategories and push it into a local newArr means I made a copy of the array. But it wasn't working when I wanted to change a value of object in it so I used Object.assign which I thought would deep copy the particular object from the array, but it's not working either.
What am I missing here?
As pointed in comments:
the code you posted does not show how you created object with unmodifiable property
you can create a new object and not use Object.assign to get rid of the error
use map function in more idiomatic way
interface TaggedItem {
name: string,
tag: string
}
var subcategories = [{name:'gloves', tag:'wool'}, {name:'boots', tag: 'leather'}];
function setSubcat(val: string, index: number, _lang: any){
var obj: Partial<TaggedItem> = {
'name': val
}
var newArr: TaggedItem[] = subcategories.map((val, i)=>{
if(index === i){
return {
...val,
...obj
}
} else {
return {...val};
// or return val; if you don't need a full copy
}
})
console.log(newArr);
}
setSubcat('newName', 0, undefined);
Playground link

Angular/TypeScript update element of array of objects

please forgive if my question is stupid but I have no idea what am I doing wrong.
I have array of objects
export interface IOptionsData {
option: string;
optionPrice: number;
benefit: string;
benefitPrice: number;
oneshotPrice: number;
commitmentPeriod: number;
}
formData: IOptionsData[] = [];
And saving data from form in it
onOptionChange(i) {
console.log(i);
console.log(this.productForm.controls['user_options']);
this.formData[i].option = this.productForm.controls['user_options'].value[i].option;
this.formData[i].commitmentPeriod = this.productForm.controls['user_options'].value[i].commitmentPeriod;
console.log(this.formData);
...
}
On first element everything seems normal
My form data:
And my array data:
But when I update another item in form, instead of updating that element in array, it updates complete array to that values
onChange function is called with correct parameter, there is no multiple functions calls
Have no idea why is this happening
Please if you know any reason why it would
Thank you
EDIT: forgot to mention
Before I went with array of objects, I used individual arrays for each value I need to store.
selectedOptions: string[] = [];
selectedBenefits: string[] = [];
selectedPrices: number[] = [];
benefitPrices: number[] = [];
oneshotBenefitPrices: number[] = [];
And same update worked as intended, only updated value is getting updated
this.selectedOptions[i] = this.productForm.controls['user_options'].value[i].option;
EDIT No2:
To reproduce this error you don't need html template.
Here is part of .ts file
export interface IOptionsData {
option: string;
optionPrice: number;
benefit: string;
benefitPrice: number;
oneshotPrice: number;
commitmentPeriod: number;
}
demoOptionsData: IOptionsData = { option: null, optionPrice: 0, benefit: null, benefitPrice: 0, oneshotPrice: 0, commitmentPeriod: 24 };
ngOnInit() {
/* Initiate the form structure */
this.formData.push(this.demoOptionsData);
this.formData.push(this.demoOptionsData);
this.formData.push(this.demoOptionsData);
this.formData[1].option = 'TEST';
console.log(this.formData);
}
Solved by comment from #htn
formData is an array of the SAME REFERENCE demoOptionsData, so, the result is expected

Why can't I use the array's append() in Swift?

I'm getting a: Cannot invoke 'append' with an argument list of type '([Book])' It works find if I use the += but I don't understand why append() won't work.
struct Book
{
var title:String
var pageCount:Int
}
class Library
{
var onShelfBooks:[Book] = []
var onLoanBooks:[Book] = []
var books:[Book]
{
get
{
return onShelfBooks + onLoanBooks
}
set(newBook)
{
onShelfBooks.append(newBook)
}
}
}
struct Book
{
var title:String
var pageCount:Int
}
class Library
{
var onShelfBooks:[Book] = []
var onLoanBooks:[Book] = []
var books:[Book]
{
get
{
return onShelfBooks + onLoanBooks
}
set(newBook)
{
onShelfBooks.append(newBook[0])
}
}
}
var myLibrary = Library()
var newBook = Book(title: "Swift Development with Cocoa", pageCount: 453)
myLibrary.books = [newBook]
myLibrary.books
Append only allows you to add one object at a time while += allows you to combine an array of objects with another object. When you call append on the setter you are trying to add an array of book objects, or [Book] instead of just a single book object.
If you would like to add [newBook] with append, you can use : of
1- onShelfBooks.append(contentsOf: newBook)
"contentOf" is type of Sequence.
otherwise use of:
2- onShelfBooks += newBook

Resources