Reputation: 736
I'm facing some weird (to me) problem with objects and types in typescript/angular.
I have some class describing my objects received from remote API:
export class Point {
lat:number;
lng:number;
name:string;
public doSomething() {
console.log("doSomething called");
}
}
I'm using HttpClient
to get objects from API:
constructor(private http:HttpClient) {}
getPointsAsync(callback: (points: Point[]) => {}) {
this.http.get<Point[]>(`${environment.apiUrl}/api/points`)
.subscribe(
(result: Point[]) => {
//do something with data
callback(result);
},
error => { //some error handling
}
);
}
My problem is that when I try to call method doSomething
on one of my Point from array
var firstPoint = points[0];
firstPoint.doSomething()
I get some weird error on console:
ERROR TypeError: firstPoint.doSomething is not a function
I'm not using Typescript and Angular for very long so I'm assuming that it is something wrong with my code but I couldn't find answer to my issue. Could you give me some advice?
Upvotes: 3
Views: 642
Reputation: 1830
Object.assign()
will solve your current problem, but not if you have nested objects. Then you will have to do Object.assign()
for each nested object as well, which can get tedious if you have to do this in multiple places in your codebase.
I suggest an alternative: class-transformer With this you can mark your nested fields with annotations that tell the compiler how to create the nested objects as well. With this you only need to use the plainToClass()
method to map your top level object and all the underlying fields will also have the correct types/objects.
Let's say we have two classes:
class Parent {
name: string;
child: Child;
public getText(): string {
return 'parent text';
}
}
class Child{
name: string;
public getText(): string {
return 'child text';
}
}
The first case we already know doesn't work:
let parentJson: any = {name: 'parent name', child: {name: 'child name'}};
let parent: Parent = parentJson; // note: compiler accepts this because parentJson is any.
// If we tried to assign the json structure directly to 'parent' it would fail because the compiler knows that the method getText() is missing!
console.log(parent.getText()); // throws the error that parent.getText() is not a function as expected
Second case using Object.assign()
:
let parentJson: any = {name: 'parent name', child: {name: 'child name'}};
let parent: Parent = Object.assign(parentJson);
console.log(parent.getText()); // this works
console.log(parent.child.getText()); // throws error that parent.child.getText() is not a function!
to make it work, we would have to do the following:
let parentJson: any = {name: 'parent name', child: {name: 'child name'}};
let parent: Parent = Object.assign(parentJson);
parent.child = Object.assign(parentJson.child);
console.log(parent.getText()); // this works
console.log(parent.child.getText()); // this works
Third case with class-transformer:
First modify the parent class so that the child mapping is defined:
class Parent {
name: string;
@Type(() => Child)
child: Child;
public getText(): string {
return 'parent text';
}
}
then you can map to the parent object:
let parentJson: any = {name: 'parent name', child: {name: 'child name'}};
let parent: Parent = plainToClass(Parent, parentJson);
console.log(parent.getText()); // this works
console.log(parent.child.getText()); // this works
Upvotes: 1
Reputation: 249506
The problem is that you don' actually get instances of Point
from the web service. You get a JSON object that has the class fields, but not the methods. You can use instantiate the class and use Object.assign
to assign the values from the object literal to each Point
instance
getPointsAsync(callback: (points: Partial<Point>[]) => {}) {
this.http.get<Point[]>(`${environment.apiUrl}/api/points`)
.subscribe(
(result: Partial<Point>[]) => {
let points = result.map(p=> Object.assign(new Point(), p));
callback(points);
},
error => { //some error handling
}
);
}
Upvotes: 3