Create a Variable with an Object Property - Angular - javascript

I'm trying to create a variable to set one of the properties of an object obtained by the get method.
When I give console in subscribe I retrieve the value of the array, but I'm having difficulty (i'm beginner) to set only one of the properties of the objects in that array.
Component:
this.mainService.getGraph()
.subscribe(res => {
console.log(res)
this.name = res[''].map(res => res.name)
console.log(this.name)
Console.log:
(5) […]
​
0: Object { name: "Carlos", lastname: "Moura", participation: 5 }
​
1: Object { name: "Fernanda", lastname: "Oliveira", participation: 15 }
​
2: Object { name: "Hugo", lastname: "Silva", participation: 20 }
​
3: Object { name: "Eliza", lastname: "Souza", participation: 20 }
​
4: Object { name: "Anderson", lastname: "Santos", participation: 40 }
​
length: 5
​
<prototype>: Array []
main.component.ts:26:6
ERROR TypeError: "res[''] is undefined"
ngOnInit main.component.ts:27
RxJS 13
Angular 8

You are redefining res in your passed in function to map.
Use a plural of name to names, you want a string array so the plural is more appropriate and conveys what the field contains.
Do not try to access a non existent field or index on res, you had res[''] which is not correct.
I put the call in ngOnInit, it might be located elsewhere but this allowed me to define the assigned variable member above it.
names: string[];
ngOnInit() {
this.mainService.getGraph()
.subscribe(res => {
console.log(res);
this.names = res.map(_ => _.name);
console.log(this.names);
}
From the comments:
...the IDE says property 'map' doesnt exist on type 'Object'. would it just be a bug
About your service. Make sure the signature is correct on the return type. Here is an example, you can also define an interface and return that instead of {name:string} (but keep the [] which denotes there is an array being returned).
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
export class MainService {
constructor(private readonly http: HttpClient){}
getGraph() : Observable<{name: string}[]> {
return this.http.get<{name: string}[]>('/some/url');
}
}

Just use res.map(res => res.name). instead of res[''].map(res => res.name). In your case you are trying to access property with key equal to empty string inside of your object, and it doesn't exist

You can do it directly in the http get Method
this.http.get(url).pipe(
map(res => {
res['newProperty'] = 'value';
return res;
})
);
Even if you want just call back just one property
this.http.get(url).pipe(
map(res => {
return res.name;
})
);

Related

Nested Array forEach not working, is typeof object in console log

I'm using mongodb, nodejs and angular (v13.3.0) where i have a model with a nested array (array of arrays).
Data is fetched like this ...
export interface Post {
_id: string;
topicId: string;
url: string;
title: string;
description: string;
subject: string;
type: PostType | string;
lessonDate: string;
lastUpdate: string;
schoolWeek: number;
}
export interface PostMatching extends Post {
elements: MatchingPair[][];
}
export type PostTypes = PostArticle | PostQuiz | PostMatching | PostIndexCards;
getPost(postUrl: string): Observable<PostTypes> {
return this.httpClient.get<PostTypes>(`${this.POSTS_ENDPOINT}/url/${postUrl}`).pipe(
map((response) => {
// console.log('response GET post', response);
return response;
}),
);
}
on the nested array i try to do a forEach() loop but the reference is typeof object so i get the following error ...
const pairs = this.matching.elements[this.round];
pairs.forEach((pair) => {
...
});
I already checked the mongodb document, the nested array is indeed an array in the database.
My workaround looks like this ...
// TODO: check why array in array is typeof object
const pairs = Object.values(this.matching.elements[this.round]);
I'm trying to figure out what is the problem here, i already checked the value with Array.isArray() which returns false, i also logged the value in the console and it seems like the array is actually a object.
Anyone had this problem before? Thanks in advance :)

Nested functions in typescript gives errors

I am not sure how you are supposed to nest functions in TypeScript. I was doing it similar to Javascript and got a lot of errors.
I have this in my typescript file (this is basically the only thing there):
ngAfterViewInit(): void {
d3.csv('data/data.csv').then((data: Array<String> => {
data.forEach(d:string =>{
d.age = Number(d.age)
})
})
}
And my data.csv file has this:
name,age
Lam,30
Peter,90
Aaron,2
Swizt,14
Irene,36
Travis, 55
I get errors such as
"Argument of type '(data: String[], { data, forEach }: any) => any' is not assignable to parameter of type '(value: DSVRowArray<string>) => any'.ts(2345)"
, and
"A parameter initializer is only allowed in a function or constructor implementation.ts(2371)"
The compiler is telling you that you provided the wrong types for your callback function. d3.csv returns a DSVRowArray<string> not a String[]. You've also forgot to close the bracket after .then((data: Array<String> hence why { data, forEach }: any is showing up in the argument list. string is also the wrong type for the foreach loop, it should be a DSVRowString.
Also, the compiler will prevent you from assigning a number value to a property of a DSVRowString, as the d3 devs have specified that only string properties are allowed. So if you want an object with number values you should create a new object.
You can define the DSVRowArray<string> and DSVRowString types in your callbacks but it's not necessary. The compiler already knows the types according to the type definitions provided by the d3 devs. So you can just exclude them, intellisense and tsc will infer the correct type.
ngAfterViewInit(): void {
d3.csv('data/data.csv').then((data) => {
data.forEach((d) => {
const convertedData = { age: Number(d['age']) };
});
});
}
If you absolutely need to keep the same structure, you can just typecast the DSVRowString to any. This is just a way to make the compiler leave you alone. Not recommended though.
ngAfterViewInit(): void {
d3.csv('data/data.csv').then((data) => {
data.forEach((d: any) => {
d.age = Number(d.age);
});
});
}
There is also an overload for the csv function, where you can provide a function describing how to parse each row. In the below function data has the type d3.DSVParsedArray<{age: number}> which I get from intellisense after hovering over the variable.
ngAfterViewInit() {
d3.csv('data/data.csv', (d) => {
return { age: Number(d['age']) };
}).then((data) => console.log(data));
}
Logging data gives me:
Array(6) [ {…}, {…}, {…}, {…}, {…}, {…} ]
0: Object { age: 30 }
1: Object { age: 90 }
2: Object { age: 2 }
3: Object { age: 14 }
4: Object { age: 36 }
5: Object { age: 55 }
columns: Array [ "name", "age" ]
length: 6
<prototype>: Array []
Which seems to just be an extension of Array with the added columns property.

What is this data structure with default property and type-like object in JS?

I notice that in some JS objects, they have properties with some special behaviours. For example, the model objects in Sequelize, if I log a model to the console, I see that the object contains properties such as _data, _changed, _modelOptions, etc.
However, when accessing the object itself, the _data property appears to be its default property. For instance:
const userModel = UserModel.findOne(...);
console.log(userModel.email); // this prints the email field of the record
console.log(userModel._data.dataValues.email) // this also prints the email of the record
It appears that I don't have to access the email from _data.dataValues. I can access it directly from the userModel object.
When I print the whole object, I also notice that the values in _data.dataValues get printed.
With this:
console.log(JSON.stringify(userModel))
I will get this result:
{
name: 'John',
email: 'john#smith.com'
}
But with this:
console.log(userModel)
I will get this instead:
t {
_data: user { // what is that 'user' before the object? is it a type definition?
dataValues: {
name: 'John',
email: 'john#smith.com'
}
_previousDataValues: {
name: 'John',
email: 'john#smith.com'
}
_modelOptions: {
...
}
...
}
}
This looks a little different from the usual JS object in that it seems to have a "type" of sort to the object those properties are "internal" and not visible when printed out.
I thought it was a class at first but I tried printing a class I've created and compared the output with the console output from this model and they look different.
I don't see this data structure in JS often. What is this data structure in JS and in Node specifically? What is different and useful about this "special" object compared to the regular object in JS?
Achieving these form of behaviour is nothing special. This can be done easily using Object.defineProperty or Object.defineProperties.
Here is an example
function Person(fName, lName) {
this._data = {
fName: fName,
lName: lName
}
Object.defineProperties(this, {
fName: {
get : function () {
return this._data.fName;
},
set: function (value) {
this._data.fName = value;
}
},
lName: {
get : function () {
return this._data.lName;
},
set: function (value) {
this._data.lName = value;
}
}
});
}
const ratul = new Person("Ratul", "sharker");
console.log(ratul);
console.log(ratul.fName);
ratul.fName = "Ra2l";
console.log(ratul.fName);
Here the enumerable property is by default set to false (check define properties documentation.) If you set it to true then it will appear in console.log(ratul).
Primary usage of these sort of behaviour in sequelize is to keep track of value changes. Directly from Sequelize github.
setDataValue(key, value) {
const originalValue = this._previousDataValues[key];
if (!_.isEqual(value, originalValue)) {
this.changed(key, true);
}
this.dataValues[key] = value;
}
Most obvious reason to track data value changes is to while calling Model.save, then sequelize can optimise which attributes/properties should sequelize write to db.
This is where Object.defineProperty is being used, which is declared within refreshAttributes, which is called from init.
Object.defineProperty(this.prototype, key, attributeManipulation[key]);

Typescript / Angular 7: How can I iterate over Object matching my Interface with Object.keys?

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>

Strange error when serializing to JSON an array of objects which have a toJSON method

I use a NSwag to generate TypeScript typings and classes for swagger API endpoints. The resulting classes contain a .toJSON() method for each object which gets called when serializing objects to JSON using JSON.stringify().
All works fine when serializing a single object, but when I try to serialize an Array of objects it throws a weird error:
angular.js:14199 TypeError: Cannot create property 'code' on string '0'
at Dashboard.toJSON (App/models/api.js:785:34)
at JSON.stringify (<anonymous>)
and the code that triggers it is pretty simple:
console.log(JSON.stringify([
Dashboard.fromJS({
code: "1212312",
name: "tresads",
description: "some description"
}),
Dashboard.fromJS({
code: "1212312",
name: "tresads",
description: "some description"
})
]));
An excerpt of the class:
export class Dashboard implements IDashboard {
code?: string | undefined;
...
constructor(data?: IDashboard) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}
init(data?: any) {
if (data) {
this.code = data["code"];
...
}
}
static fromJS(data: any): Dashboard {
let result = new Dashboard();
result.init(data);
return result;
}
toJSON(data?: any) {
data = data ? data : {};
data["code"] = this.code;
...
return data;
}
clone() {
const json = this.toJSON();
let result = new Dashboard();
result.init(json);
return result;
}
}
Any idea why JSON.stringify() calls the toJSON() method with the "0" parameter?
The method toJSON will be called with one argument, which is the property name to which this is assigned. In essence, the value of your interest is not that argument, but this, which will be bound to the value that you could transform. Since you call stringify with an array, toJSON will get called with the enumerable properties of that array, i.e. 0 and 1, while this will be the corresponding Dashboard object.
Also, I have the impression you could make good use of Object.assign which will copy properties from one object to another, which is essentially what you do in the constructor's for loop.
So here is how you could do it. I removed the typescript decoration and used plain JavaScript, but the principle remains the same:
class Dashboard {
constructor(data) {
// Object.assign does essentially what you want with the loop:
Object.assign(this, data);
}
init(data) {
return Object.assign(this, data);
}
static fromJS(data) {
return new Dashboard(data);
}
toJSON(key) {
// `key` is the key/index of the property in the parent object.
// That probably is of no interest to you. You need `this`.
// Extract properties into plain object, and return it for stringification
return Object.assign({}, this);
}
clone() {
return new Dashboard(this);
}
}
console.log(JSON.stringify([
Dashboard.fromJS({
code: "1212312",
name: "tresads",
description: "some description"
}),
Dashboard.fromJS({
code: "1212312",
name: "tresads",
description: "some description"
})
]));
Actually, in the given example, you don't need toJSON at all, since the properties of the Dashboard instance are enumerable, and so they will be stringified anyway. If for some reason you do need it for a certain transformation to happen, then of course you still need to include the logic for that transformation, since Object.assign is just a simple copy.

Categories

Resources