I am using Angular. I need to iterate over an object to get the required values from an array. In this case, the "entries" method is not available. How to solve this problem?
searchNode(items: { [key: string]: any }) {
for (let [index, element] of items.entries()) {
console.log(index)
console.log(element)
}
}
See picture here
Related
I have class with a property member of type array, an item in the array can be of several types for example MetaViewDatalinked or MetaViewContainer, see example below
class MetaViewContainer{
children: (MetaViewDatalinked | MetaViewContainer)[];
}
class MetaViewDatalinked{
id: string;
}
I want to use class-transformer (plainToClass(MetaViewContainer, json)) to initialize the class with its properties.
if I had class without array see below, I would use #Type
class MetaViewContainer{
#Type((typeHelpOptions: TypeHelpOptions) => {
if (typeHelpOptions.object.container)
return MetaViewContainer;
return MetaViewDatalinked;
})
child: MetaViewDatalinked | MetaViewContainer;
}
The issue is #Type is called only once regardless of the property's type: a class or an array of classes, thus creating an array (in case of property being array) of single type.
Any ideas how to solve the issue are really appreciated.
I solved the issue with using #Transform decorator
class MetaViewContainer{
#Transform((params: TransformFnParams) => {
const children: (MetaViewDatalinked | MetaViewContainer)[] = [];
if (!params.value) return children;
if (Array.isArray(params.value)) {
for (const item of params.value) {
if (item.children)
children.push(plainToClass(MetaViewContainer, item));
else
children.push(plainToClass(MetaViewDatalinked, item));
}
}
return children;
})
children: (MetaViewDatalinked | MetaViewContainer)[];
}
I've run into an issue with the serialization of classes that I don't know how to handle.
I create objects from REST or database requests requests like this:
export interface ILockerModel {
id: string
lockerId: string
ownerId: string
modules: IModuleModel[]
}
export class LockerModel implements ILockerModel {
private _id: string
private _lockerId: string
private _ownerId: string
private _modules: ModuleModel[]
constructor(document: ILockerModel) {
this._id = document.id
this._lockerId = document.lockerId
this._ownerId = document.ownerId
this._modules = document.modules.map(m => new ModuleModel(m))
}
// Utility methods
}
I then have multiple utility methods that make it easier to work with the model, adding and removing things from lists and so on.
When I'm done I want to persist the object to a document database or return it in a REST response, so I call JSON.stringify(objectInstance). However, this gives me the class but with all properties underscored (_), not my getter values. This breaks the deserialization in other parts of my application.
Serializing the interface gives me what I want, but I haven't found a straightforward way to go from the class to the interface representation. The issue gets tougher because I deserialize the data in a hierarchy (see the modules mapping in the constructor).
How do you usually solve this issue?
As far as I can see you do not really implement the ILockerModel. Shouldnt this throw an error?
When I run it, I get the following:
Type 'LockerModel' is missing the following properties from type 'ILockerModel': id, lockerId, ownerId, modules
The other thing is that JSON.strigify() just takes your object and makes a string representation of all its properties. It does not care about your getters. If you want it to transform it to the right format, you should give it an object in the correct format.
One solution would be to just remove the '_' from all the keys, by using a combination of map and reduce:
const input = {
_test: 123,
_hello: 'world'
};
console.log(input);
console.log(JSON.stringify(input));
const convertToJson = (obj) => {
return Object.entries(obj) // Create array from object
.map(([key, value]) => [ // change key to remove '_'
key.startsWith('_') ? key.substring(1) : key,
value
])
.reduce((acc, [key, value]) => { // Transform back to object
acc[key] = value;
return acc;
}, {});
}
const output = convertToJson(input);
console.log(output);
console.log(JSON.stringify(output));
Or if you are allowed to use ES10:
const input = {
_test: 123,
_hello: 'world'
};
console.log(input);
console.log(JSON.stringify(input));
const convertToJson = (obj) => {
return Object.fromEntries( // Create Object from array
Object.entries(obj) // Create array from object
.map(([key, value]) => [ // change key to remove '_'
key.startsWith('_') ? key.substring(1) : key,
value
])
);
}
const output = convertToJson(input);
console.log(output);
console.log(JSON.stringify(output));
bellow the class i initialize an object of type Photo, which photo is an interface with some attributes. And then i'm trying to use array filter to filter photos.
The filter return a copy of the photos array but i specify which photo do i want so i will have an array with 1 index inside. And then, i try to set a property inside the object to false, and the "Cannot set property 'ismain' of undefined in angular" occurs. Any ideas why?
Here is my code.
setMainPhoto(photo: Photo) {
this.userService
.setMainPhoto(this.authService.decodedToken.nameid, photo.id)
.subscribe(
() => {
// We use the array filter method to filter the photos apart from the main photo
// Filter returns a copy of the photos array. Filters out anything that it doesn`t match in the p
this.currentMain = this.photos.filter(p => p.ismain === true)[0];
this.currentMain.ismain = false;
photo.ismain = true;
this.alertify.success('Successfully set to profile picture');
},
error => {
this.alertify.error('Photo could not be set as profile picture');
}
);
}
In the above method the error occurs.
And below is my Photo interface.
export interface Photo {
id: number;
url: string;
dateadded: Date;
ismain: boolean;
description: string;
}
ERROR
UPDATE
PROBLEM SOLVED
The response from the server needs to match with the object attributes. So i had to change the property interface to match the JSON object coming back from the server.
Try this
setMainPhoto(photo: Photo) {
this.userService
.setMainPhoto(this.authService.decodedToken.nameid, photo.id)
.subscribe(
() => {
// We use the array filter method to filter the photos apart from the main photo
// Filter returns a copy of the photos array. Filters out anything that it doesn`t match in the p
this.currentMain = this.photos.filter(p => p.ismain === true)[0];
if (this.currentMain) {
this.currentMain.ismain = false;
photo.ismain = true;
this.alertify.success('Successfully set to profile picture'); }
},
error => {
this.alertify.error('Photo could not be set as profile picture');
}
);
}
If this works, then the filter does not return anything.
Interesting, I've had this come up before too. Typescript is quite strict with capitalization and if you don't match the json object exactly, it won't work, create a new property, etc.
I've found it helpful to take a look at the json return before building my typescript model, to make sure I'm matching the object being sent.
I am pretty new to Javascript and Typescript/Angular..
I am getting data / game stats from a website to make my own stats app. I have created an Interface with defined Key/Values to match the data that I want to show on my template.
The API's response that I wish to show is in the form of nested objects. Because of this, I am using Object.keys() in order to iterate through the objects, and show the result on my HTML template.
I am able to show JSON of the specific nested Object just fine. My problem is: it is showing the ENTIRE Key/Values of that Object, and not the specific key values I have defined in my Interface.
Within my HTML, I am looping through my Interface called lifeTimeStat Object.keys(lifeTimeStat) - (also please see below for full html)
Seeing as how I need to iterate through an Object, I attempted to make my lifeTimeStat Interface an object.. Like this:
`export class CombatListComponent implements OnInit {
constructor(public combatService: CombatService) { }
lifeTimeStats: lifeTimeStat = {
headshotKills: null,
kills: null,
longestKill: null,
maxKillStreaks: null,
dailyKills: null,
weeklyKills: null,
damageDealt: null,
roadKills: null,
teamKills: null,
vehicleDestroys: null,
suicides: null,
roundMostKills: null,
dBNOs: null,
assists: null
}
ngOnInit() {
this.combatService.getCombat().subscribe(data => {this.lifeTimeStats =
data} );
}
}
`
But of course, however, I am getting this error: TypeError: Cannot read property 'keys' of undefined .. So it seems I am not making my Interface into an Object the right way..
The only way that I can get the JSON to show in my HTML is if I instead define
Object = Object instead of that crappy attempt of turning my Interface into an object.. Argh. Object = Object will just show the entire Object and not the specific shape of my Interface..
HTML: combat-list.component.html
<div *ngFor="let key of Object.keys(lifeTimeStat)"> {{ lifeTimeStats[key].attributes.gameModeStats.solo| json }} </div> <br>
Service Component: combat.service.ts
`#Injectable({
providedIn: 'root'
})
export class CombatService {
getCombat():Observable<lifeTimeStat> {
return this.http.get<lifeTimeStat>(this.configUrl, { observe:'body',
responseType: 'json', headers: getHeaders });
}`
Interface:
`export interface lifeTimeStat {
headshotKills: number,
kills: number,
longestKill: number,
maxKillStreaks: number,
dailyKills: number,
weeklyKills: number,
damageDealt: number,
roadKills: number,
teamKills: number,
vehicleDestroys: number,
suicides: number,
roundMostKills: number,
dBNOs: number,
assists: number,
} `
I just want to show the selected data as I have defined in my Interface. I've been googling / searching on SO for many hours over the course of two, almost three days :(
So this syntax should work:
Object
.keys({ a: 1, b: 2 })
.map(key => console.log(key));
If not, why don't you assign the array to a variable and iterate over the variable?
In any case, you can use this nice helper to preserve types and iterate over both keys and values:
const testObject = {
completed: true,
score: 129.1,
description: 'none',
};
entries(testObject)
.map(([key, value]) => console.log(`Key is ${key}, value is ${value}`));
function entries<K>(object: K) {
return (Object.keys(object) as (keyof K)[])
.filter((key) => object[key] !== undefined && object[key] !== null)
.map(
key => ([
key,
object[key],
] as [keyof K, Required<K>[keyof K]]),
);
}
type Required<T> = {
[P in keyof T]-?: T[P];
};
Or for your case:
const keyValuePairs = entries(lifeTimeStat);
...
<div *ngFor="const [key, value] of keyValuePairs"> {{ value.attributes.gameModeStats.solo| json }} </div> <br>
I'm going to explain the context I have before explain the problem design a service with Angular and RxJS.
I have one object with this model:
{
id: string;
title: string;
items: number[];
}
I obtain each object of this type (called "element") through GET /element/:id
Each element has an items: number[] that contains an array of numbers and each number has an URL so I do a new GET /element/:id/:itemNumber for each number and return the items detail. That item detail model is like:
{
idItem: string;
title: string;
}
So in my service I want to serve to the components a method that it will return an Observable, it will obtain an array of objects, where each object has the element model with one addition property that will be an array of its detailed items. So, I'm going to show what I have:
The service:
getElement(id: string): any {
return this.http.get<Element>(this.basePath + `/element/${id}`).pipe(
map(element => this.getAllItemsDetail(element))
}
getAllItemsDetail(element: Element): any {
return of(...element.items).pipe(
map(val => this.getItemDetail(element.id, val)),
concatAll()
);
}
My problem is, I'm not understanding how I can, in RxJS, after the map(element => this.getAllItemsDetail(element)) in the getElement method, merge the items array returned by it and the previous element I have before the map that has the element object. I need to add "anything" after that map to compute an Object.Assign to merge the both objects.
EDIT
With the answer of #chiril.sarajiu, he gives me more than one clue, with switchMap I can map the observable (map is used for objects, "do a compute with the result object"). So my code, it's not the most pretty version I know..., I will try on it, but it works. So the final code looks like this:
getElement(id: string): any {
return this.http.get<Element>(this.basePath + `/element/${id}`).pipe(
switchMap(element => this.getAllItemsDetail(element)),
map(result => { return Object.assign({}, result.element, {itemsDetail: result.items})})
);
}
getAllItemsDetail(element: Element): any {
const results$ = [];
for (let i = 0; i < element.items.length; i++) {
results$.push(this.getItemDetail(element.id, element.items[i]));
}
return zip(...results$).pipe(
map(items => { return Object.assign({}, { element, items })})
);
}
Be aware of the possibility about zip will not emit values if the results$ is empty, in that case you can add of(undefined) to the results$ array and after take into account that items will be [undefined]
To map another observable you should use switchMap function instead of map
So
getElement(id: string): any {
return this.http.get<Element>(this.basePath + `/element/${id}`).pipe(
switchMap(element => this.getAllItemsDetail(element))
}
UPDATE
Consider this medium article