Access nested object properties in json object - json

how can I loop through the following object in Angular using *ngFor(suppousing there are many objects)? I can't find how to access "type" property. Also I wonder whether "Animals Catalog" property is correct? Thanks in advance.
{
"name":"Richard",
"lastname":"Garcia",
"age":32,
"pets":{
"Animals Catalog":{
"type":"cat",
"gender":"male"
}
},
}

that property is correct and you have to access it like this:
<h1 *ngFor="let item of items">{{item.pets['Animals Catalog'].type}}</h1>

you need to declare and access object.keys to use it in template
app.component.ts
objectKeys = Object.keys;
obj = {
name: 'Richard',
lastname: 'Garcia',
age: 32,
pets: {
'Animals Catalog': {
type: 'cat',
gender: 'male',
},
},
};
app.component.html
<div *ngFor="let key of objectKeys(obj)">{{ key + ' : ' + obj[key] }}</div>
reference: Object.keys()

Based on your code example:
interface
export interface User {
name: string;
lastname: string;
age: number;
pets: Pet;
}
export interface Pet {
[key: string]: PetDetail;
}
export interface PetDetail {
type: string;
gender: string;
}
component
<div *ngFor="let user of users">
<p>{{ getValue(user) }}</p>
</div>
#Component(...)
export class Component {
getValue(user: User): string {
const keys = Object.keys(user);
for (const key of keys) {
const isPetObject = this.validateType(user[key]);
if (!isPetObject) return user[key];
const pets = user[key];
const petKey = Object.keys(pets)[0]; // 'Animal Catalog'
return pets[petKey]?.type; //cat
}
}
private validateType(value: any): boolean {
return typeof value !== string && typeof value !== number;
}
}

Related

Send Angular API GET request value to a variable

I am trying to display the data of an object on Angular like so
{{myCharactere && myCharactere.statistics && myCharactere.statistics[stat.key] || ''}}.
The object is issue from an API GET request but I'm not able to send it's value to my local variable myCharactere. Thank you for helping me out! Edit: Added code for clarification
Here is what I tried
TypeScript component
export class StatsComponent implements OnInit {
myCharactere: any;
statLookup = [
// Pourquoi est-ce un mauvais choix???
{ key: 'str', prefix: $localize`str`, suffix: $localize`enght`, couleur: 'bg-danger' },
{ key: 'dex', prefix: $localize`dex`, suffix: $localize`terity`, couleur: 'bg-primary' },
{ key: 'con', prefix: $localize`con`, suffix: $localize`stitution`, couleur: 'bg-warning' },
{ key: 'int', prefix: $localize`int`, suffix: $localize`elligence`, couleur: 'bg-success' },
{ key: 'sag', prefix: $localize`wis`, suffix: $localize`dom`, couleur: 'bg-info' },
{ key: 'cha', prefix: $localize`cha`, suffix: $localize`risma`, couleur: 'bg-dark' }
];
constructor(public myService: ServeurService) { }
ngOnInit(): void {
this.myService.getAllInfosById(this.myService.getPersonnageIdByName("Xefoul Snaromers")).subscribe(result => {
this.myCharactere = result
console.log(this.myCharactere);
});
getModifier(stat: number): string {
const mod = Math.floor((stat-10)/2)
return (mod<0)?'-':'+'+ mod.toString();
}
}
TypeScript Service
export class ServeurService {
personnages: any[] = [];
persoName = '';
constructor(private http_client: HttpClient) { }
getPersonnage(): Observable<ICharactere[]> {
return this.http_client.get<ICharactere[]>(this.serverCharacter).pipe(retry(4));
}
getAllInfosById(id: string) {
const myUrl = 'https://cegep.fdtt.space/v1/character/' + id;
return this.http_client.get<ICharactere>(myUrl).pipe();
}
setPersonnageName(name: string) {
this.persoName = name;
}
getPersonnageName():string {
return this.persoName;
}
getPersonnages() {
this.http_client.get<any>('https://cegep.fdtt.space/v1/characters').subscribe({
next: (val) => {
val.data.forEach((element: { data: any; }) => {
this.personnages.push(element);
});
}
});
return this.personnages;
}
getPersonnageById(id: string) {
const persoSelectionne = this.getPersonnages().find((x: { id: string; }) => x.id === id);
return persoSelectionne;
}
getPersonnageIdByName(name: string) {
const persoSelectionne = this.getPersonnages().find((n: {name: string; }) => n.name === name);
console.log("perso name service", persoSelectionne)
return persoSelectionne.id;
}
}
HTML to display
<div class="row text-center text-light bg-secondary mt-2 bg-transparent">
<div class="row mt-5">
<div class="col-2" *ngFor="let stat of statLookup">
<div class="{{stat.couleur}} mx-xxl-4 mx-2 mx-md-1 rounded-4">
<div class="fw-bold">{{stat.prefix}}<span class="d-none d-lg-inline">{{stat.suffix}}</span>
</div>
<div class="h2">
{{myCharactere && myCharactere.statistics && myCharactere.statistics[stat.key] ? getModifier(myCharactere.statistics[stat.key]) : ''}}
</div>
<div class="">
{{myCharactere && myCharactere.statistics && myCharactere.statistics[stat.key] || ''}}
</div>
</div>
</div>
</div>
Model if it helps
export interface ICharactere {
error: string;
id: string;
name: string;
statistics: { [ key : string ]: number }
race: string;
player: string;
classe : string;
sousclasses: string;
level: number;
background: string;
synopsis: string;
image: string;
health: number;
currentHealth: number;
traits: {
trait: string;
description: string;
}[];
armorClass: number;
initiative: number;
speed: number;
}
Summarized from comments:
In your component you can use
serviceName.getAllInfosById("demochar").subscribe(console.log)
to manually make the request to the API and log the result. Please be aware that this.http_client.get<ICharactere>(myUrl) returns a cold observable. This means that nothing will be done until you actually call .subscribe to it.
Best practice:
Usually when you want to display data from an observable in your HTML template you define an observable and subscribe to it using async pipe.
The way to do this is to first define the observable in your component, like: info$ = this.serviceName.getAllInfosById("demochar").
Now in your HTML template you can use {{ (info$ | async).name }} to first subscribe to the observable (async pipe does this for you) and display the name property of the emitted value.
If you are actually using an observable like this.http_client.get<ICharactere>(myUrl), another way is to await the return value and store it in a this.myCharactere:
async getInfos(): void {
this.myCharactere = await firstValueFrom(this.myService.getAllInfosById(this.myService.getIdPersonnageByName("Xefoul Snaromers")));
}

Get name from ID String with MEAN

I'm using the MEAN Stack technology in a project, and I have the following schemas on frontend:
export interface ProjectSchema {
name: string;
acronym: string;
startDate: Date;
endDate?: Date;
tasks: string[];
}
and
export interface TaskSchema {
_id: string;
name: string;
}
On backend, tasks from projectSchema is of type object of tasks, who saves the string ids of tasks like:
tasks: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Task' } ]
So, what I want is to write on Frontend, the tasks names instead of the string ids which is what is saved on Backend.
I tried this, but can't understand what I'm doing wrong:
...
export class ProjectInfoComponent implements OnInit {
hasProject: boolean = false;
project!: ProjectSchema;
projectTasks: any;
taskNameList!: string[];
private _getProjectByAcronymRoute() {
this.projectService.getProjectByAcronym( this.route.snapshot.params['acronym'] )
.subscribe(
{
next: project => {
this.project = project;
this.hasProject = true;
this.projectTasks = this.project.tasks.map( t => t + "\n" );
this.taskNameList = [];
for (let i = 0; i < this.project.tasks.length; i++) {
this._getTaskByIdAux(this.project.tasks[i]);
}
this.taskNameList.map( t => t + "\n");
console.log(this.taskNameList);
console.log(this.project.tasks);
}
} )
}
private _getTaskByIdAux(myid: string) {
this.taskService.getTask(myid)
.subscribe(
{
next: taskaux1 => {
this.taskNameList.push(taskaux1.name);
}
} )
}
On HTML:
...
<div class="model-info__property">
<div class="model-info__property__key">
tasks:
</div>
<div class="model-info__property__value">
{{ projectTasks }}
</div>
</div>
<div class="model-info__property">
<div class="model-info__property__key">
tasksName:
</div>
<div class="model-info__property__value">
{{ taskNameList }}
</div>
</div>
Apparently, from tests that I've made, it is getting the name correctly, but it is not writing, and it seems array is somewhat strange when I log it.
Can you help me understand what I'm doing wrong and why it is not writing anything on taskNameList pls ?

angular ngx-treeview from Json object error : A text of item must be string object

this treeview item text is confusing me for the past week
this is how I load the items into the tree view:
ngOnInit(): void {
this.items = this.getItems([JSON.stringify(this.json_obj)]);
}
getItems(parentChildObj: any[]) {
let itemsArray: TreeviewItem[] = [];
parentChildObj.forEach((set: TreeItem) => {
itemsArray.push(new TreeviewItem(set,true))
});
return itemsArray;
}
and this is how I create the nested Json object from non-nested Json file :
this.departments.forEach(element => {
if(element.ParentID == 0){
p_counter++;
this.json_obj.push(
{
text: element.DepartmentName ,
value: 'p'+p_counter.toString() ,
children: [],
id: element.DepartmentID.toString() ,
} as never
);
element.DepartmentName = 'fixed';
}
});
the template is simple as that:
<ngx-treeview [items]="items" dir ="rtl"></ngx-treeview>
btw- it creates a perfect nesting but it cant read the object properties in
new TreeviewItem(set,true);
because it's undefined.
Error : A text of item must be string object at new TreeviewItem
please help me figure this out, what can I do to make it work?
You have used
text: element.DepartmentName ,
value: 'p'+p_counter.toString() ,
children: [],
id: element.DepartmentID.toString()
It seems you have not followed TreeItem interface given in treeview-item.d.ts
export interface TreeItem {
text: string;
value: any;
disabled?: boolean;
checked?: boolean;
collapsed?: boolean;
children?: TreeItem[];
}
you should remove id because that is not property of TreeItem interface.
import { Component,OnInit } from '#angular/core';
import { TreeviewItem } from 'ngx-treeview';
#Component({
selector: 'my-app',
template: `<ngx-treeview [items]="items"></ngx-treeview>`
})
export class AppComponent implements OnInit {
items: TreeviewItem[];
ngOnInit() {
this.items = this.getItems();
}
getItems(){
// fetch api response
// convert response into this format (object can be nested, should contain below keys only with given type)
// {
// text: string;
// value: any;
// disabled ?: boolean;
// checked ?: boolean;
// collapsed ?: boolean;
// children ?: TreeItem[];
// }
const item = new TreeviewItem({
text: 'Children', value: 1, children: [
{ text: 'Baby 3-5', value: 11 },
{ text: 'Baby 6-8', value: 12 },
{ text: 'Baby 9-12', value: 13 }
]
});
return [ item ];
}
}
stackblitz example

How to map nested collection to class instance object in TypeScript

I am working on Angular application and need map nested object with collection. I have map first half of data but I am struggling to map collection, reference options[] under heading mapping Code
I have add question base class followed by DropdownQuestion class to which I am intended to map json data. structure of json data is in data model class
data model class
export class QuestionsDataModel
{
consultationId: string;
displayId: string;
displayOrder: string;
questionId: string;
questionType: string;
title: string;
questionElement: {
questionElementId: string;
questionElementTypeId: string;
questionId: string;
questionElementType: {
id:string;
typeDefination: string;
validation: string;
};
preDefineAnswer:{
questionElementId:string;
preDefineAnswerId:string;
text:string;
preDefineSubAnswer:{
preDefineAnswerId: string;
preDefineSubAnswerId: string;
subText:string;
};
};
} ;
}
Mapping code (need help here)
for(var key in questionsList)
{
let _dropBox = new DropdownQuestion({
consultationId: questionsList[key].consultationId,
questionId: questionsList[key].questionId,
questionElementId: questionsList[key].questionElementId,
questionType: questionsList[key].questionType,
title:questionsList[key].title,
key: questionsList[key].questionId,
label: questionsList[key].title,
options: [ // need help here, how to map this collection from json data
{key: 'solid', value: 'Solid'},
{key: 'great', value: 'Great'},
{key: 'good', value: 'Good'},
{key: 'unproven', value: 'Unproven'}
],
order: 1
});
Dropdown class
import { QuestionBase } from './question-base';
export class DropdownQuestion extends QuestionBase<string> {
controlType = 'dropdown';
options: {key: string, value: string}[] = [];
constructor(options: {} = {}) {
super(options);
this.options = options['options'] || [];
}
}
Question Base class
export class QuestionBase<T>{
consultationId: string;
questionId: string;
questionElementId:string;
questionType:string;
title:string;
value: T;
key: string;
label: string;
required: boolean;
order: number;
controlType: string;
constructor(options: {
consultationId?:string,
questionId?:string,
questionElementId?:string,
questionType?:string,
title?:string,
value?: T,
key?: string,
label?: string,
required?: boolean,
order?: number,
controlType?: string
} = {}) {
this.consultationId = options.consultationId,
this.questionId = options.questionId,
this.questionElementId = options.questionElementId,
this.questionType = options.questionType,
this.title = options.title,
this.value = options.value;
this.key = options.key || '';
this.label = options.label || '';
this.required = !!options.required;
this.order = options.order === undefined ? 1 : options.order;
this.controlType = options.controlType || '';
}
}
[![enter image description here][1]][1]
error after using map
You can use the Array.prototype.map function:
for(var key in questionsList)
{
let _dropBox = new DropdownQuestion({
[...],
options: questionsList[key].preDefineAnswer.map(a => {key: a.preDefineAnswerId, value: a.text},
[...]
});

Typescript object from JSON

I'm using TypeScript to build an app and I'm making API calls to retrieve objects. For instance, I have a TypeScript User Object like this:
export class User {
id : number;
name : string;
email : string;
}
And my API returns
{
"id" : 3,
"name" : "Jonn",
"email" : "john#example.com"
}
I want to convert that JSON to a User. I've read in another posts I can do this:
let user : User = <User> myJson;
This seemly works. I can access properties of the user like user.namebut my problem is that, if the User class implements some method, the properties are not available. For example, if inside the User class I have this:
getUppercaseName() : string {
return this.name.toUppercase();
}
This happens:
user.name returns John but user.getUppercaseName() returns undefined
What's going on? How to solve this
What you are doing it treating classes as interfaces, as this will work exactly the same:
export interface User {
id : number;
name : string;
email : string;
}
The reason that the compiler doesn't complain about you using classes this way is because:
One of TypeScript’s core principles is that type-checking focuses on
the shape that values have. This is sometimes called “duck typing” or
“structural subtyping”
(read more about duck typing)
Or with an example:
class User {
id: number;
name: string;
email: string;
constructor(id: number, name: string, email: string) {
this.id = id;
this.name = name;
this.email = email;
}
}
function logUser(user: User) {
console.log(`user id: ${ user.id }, name: ${ user.name }, email: ${ user.email }`);
}
logUser({
id: 1,
name: "user 1",
email: "mailaddress"
});
logUser(new User(2, "user 2", "anotheraddress"));
In the two calls to logUser I pass objects that satisfy the interface of the User class.
If you want to have an instance of that class instead of an object that satisfies it then you should do something like:
new User(myJson.id, myJson.name, myJson.email);
And have a constructor like in my example, or:
interface IUser {
id: number;
name: string;
email: string;
}
class User implements IUser {
id: number;
name: string;
email: string;
constructor(data: IUser) {
this.id = data.id;
this.name = data.name;
this.email = data.email;
}
}
...
new User(myJson);
Nitzan pretty much explained the theory behind this, so I'll just provide an alternative approach:
interface UserInfo {
id: number;
name: string;
email: string;
}
class User {
userInfo: UserInfo;
constructor(userInfo: UserInfo) {
this.userInfo = userInfo;
}
getUpperCaseName(): string {
return this.userInfo.name.toLocaleUpperCase();
}
}
const json = {
id: 3,
name: "Jonn",
email: "john#example.com"
}
let user: User = new User(json);
There is a problem when your User has 50 or more properties...
Add a constructor in your User object so that it extends your json object.
export class User {
constructor( jsonUser: any )
{
$.extend(this, jsonUser);
}
id : number;
name : string;
email : string;
getUpperCaseName() {...}
}
In your ajax callback, create the User object from your json Object:
let receivedUser = new User( jsonUser );
let userName = receivedUser.getUpperCaseName();
I detailed the solution in that post.