Send Angular API GET request value to a variable - html

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")));
}

Related

Observable data on Angular not displaying

I'm trying to pass the data of an Observable retrieved by API request to a component variable in order to display it but I just can't make it work. Please help, here is my code. Regards,
TypeScript Service : Request to API to get Observable
export class ServeurService {
serverCharacter = 'https://cegep.fdtt.space/v1/characters';
serverSecret = 'https://cegep.fdtt.space/v1/secret';
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): Observable<ICharactere> {
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;
}
TypeScript Component : Passing the Observable to a variable
export class StatsComponent implements OnInit {
myCharactere!: any;
statLookup = [
{ 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(this.myService.getPersonnageName())).subscribe(result => {
this.myCharactere = result
});
console.log("stats component perso", this.myCharactere)
}
getModifier(stat: number): string {
const mod = Math.floor((stat-10)/2)
return (mod<0)?'-':'+'+ mod.toString();
}
}
HTML : Displaying the variable
<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?.data.statistics[stat.key]}}
</div>
</div>
</div>
</div>
If it helps, here is the model too :
export interface ICharactere {
error: string;
data: {
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;
}[];
// Should be computed
armorClass: number;
initiative: number;
speed: number;
};
}
You have some fundamental concepts mixed up here. First, you're calling getPersonnages() synchronously and it is making an HTTP call which is an asynchronous operation. I understand what you're trying to do, but if you are going to use observables for your search result, then I suggest you make all of your function calls consistent that way. Here's an example:
getPersonnages(): Observable<any> {
return this.http.get<any>('https://cegep.fdtt.space/v1/characters');
}
getPersonnageIdByName(name: string) {
return new Observable(observer => {
this.getPersonnages().subscribe(
(results => {
observer.next(results.data.find(x => x.name === name))
observer.complete();
})
)
})
}
Now you can search for the ID value you want like this:
this.getPersonnageIdByName("Michel Michaud").subscribe(
(searchResult => {
// here I can get the id
const id = searchResult.id;
})
);

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 ?

Access nested object properties in json object

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;
}
}

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

Angular refresh data in child component from filtered value

I have a reusable child table component that loads dynamically based on data from the parent component.
For the first time everything loads well, however when I need to click on one of the sort columns in the table I need to send that sort property again to the parent component to return the result from api and refresh the data in the child component with a new set of data from api.
In the code it looks like this, I missed something in that refresh:
table.component.ts
export class TableComponent implements OnChanges {
#Input() items;
#Input() headers;
#Input('sortColumn') sortColumn;
#Output() sortColumnChange = new EventEmitter<string>();
ngOnChanges(changes: SimpleChanges) {
this.items;
console.log('OnChanges', changes);
}
onSortClick(event, selectedColumn) {
const target = event.currentTarget,
classList = target.classList;
let column = '';
if (classList.contains('sort-icon-asc')) {
classList.remove('sort-icon-asc');
classList.add('sort-icon-desc');
column = `${selectedColumn} DESC`;
this.sortColumn = column;
this.sortColumnChange.emit(column);
} else {
classList.add('sort-icon-asc');
classList.remove('sort-icon-desc');
column = `${selectedColumn} ASC`;
this.sortColumn = column;
this.sortColumnChange.emit(column);
}
}
}
table.component.html
<table>
<thead>
<td (click)="onSortClick($event, header.value)" *ngFor="let header of headers" class="sort-icon-asc">{{ header.name }}</td>
</thead>
<tbody>
<tr *ngFor="let item of items">
<td *ngFor="let value of item | objectValues">
{{ value }}
</td>
</tr>
</tbody>
</table>
users.component.ts
export class UsersComponent implements OnInit {
observablesDispose$: Subject<void> = new Subject();
sortColumn = 'userId ASC';
items: [];
usersTableHeaders = [
{
value: 'userId',
name: this.translateService.instant('ADMIN.USERS_TABLE.USER_ID')
},
{
value: 'name',
name: this.translateService.instant('ADMIN.USERS_TABLE.NAME')
},
{
value: 'role',
name: this.translateService.instant('ADMIN.USERS_TABLE.ROLE')
},
{
value: 'email',
name: this.translateService.instant('ADMIN.USERS_TABLE.EMAIL')
},
{
value: 'status',
name: this.translateService.instant('ADMIN.USERS_TABLE.STATUS')
}
];
constructor(
private readonly usersService: UsersService,
private readonly translateService: TranslateService
) {}
ngOnInit(): void {
this.getUsers();
}
getUsers(): void {
this.usersService
.getUsers(this.sortColumn)
.pipe(takeUntil(this.observablesDispose$))
.subscribe((users) => {
this.items = users.resultList.map((tableColumn) => ({
userId: tableColumn.userId,
name: tableColumn.displayName,
role: tableColumn.role,
email: tableColumn.email,
status: tableColumn.status
}));
});
}
ngOnDestroy(): void {
this.observablesDispose$.next();
this.observablesDispose$.complete();
}
}
users.component.html
<div class="row">
<div class="table-section">
<app-table
[headers]="usersTableHeaders"
[items]="items"
[(sortColumn)]="sortColumn">
</app-table>
</div>
</div>
EDIT
users.component.ts
export class UsersComponent implements OnInit {
observablesDispose$: Subject<void> = new Subject();
sortColumn = 'userId ASC';
items: [];
usersTableHeaders = [
{
value: 'userId',
name: this.translateService.instant('ADMIN.USERS_TABLE.USER_ID')
},
{
value: 'name',
name: this.translateService.instant('ADMIN.USERS_TABLE.NAME')
},
{
value: 'role',
name: this.translateService.instant('ADMIN.USERS_TABLE.ROLE')
},
{
value: 'email',
name: this.translateService.instant('ADMIN.USERS_TABLE.EMAIL')
},
{
value: 'status',
name: this.translateService.instant('ADMIN.USERS_TABLE.STATUS')
}
];
constructor(
private readonly usersService: UsersService,
private readonly translateService: TranslateService
) {}
ngOnInit(): void {
this.getUsers();
}
getUsers(): void {
this.usersService
.getUsers(this.sortColumn)
.pipe(takeUntil(this.observablesDispose$))
.subscribe((users) => {
this.items = users.resultList.map((tableColumn) => ({
userId: tableColumn.userId,
name: tableColumn.displayName,
role: tableColumn.role,
email: tableColumn.email,
status: tableColumn.status
}));
});
}
updateSort(newColumn: string): void {
this.sortColumn = newColumn;
this.getUsers();
}
ngOnDestroy(): void {
this.observablesDispose$.next();
this.observablesDispose$.complete();
}
}
users.component.html
<div class="row">
<div class="table-section">
<app-table
[headers]="usersTableHeaders"
[items]="items"
[(sortColumn)]="sortColumn"
(sortColumnChange)="updateSort($event)"
>
</app-table>
</div>
</div>
In your way of handle sortColumn change you cannot control change of it to update users list. In your component you should do like:
users.component.html
<div class="row">
<div class="table-section">
<app-table
[headers]="usersTableHeaders"
[items]="items"
[sortColumn]="sortColumn"
(sortColumnChange)="updateSort($event)">
</app-table>
</div>
</div>
users.component.ts
...
updateSort(newColumn: string): void {
this.sortColumn = newColumn;
getUsers();
}