ウェブサイト検索

typescript2.0 - Angular HttpClient を使用した TypeScript で、クラスメソッドを呼び出すときに ErrorType ... は関数ではありません


私はTypeScript/Angularでオブジェクトと型に関して自分には奇妙な問題に直面しています。

リモートAPIから受け取ったオブジェクトを説明するクラスがあります:

export class Point {
    lat:number;
    lng:number;
    name:string;

    public doSomething() {
        console.log("doSomething called");
    }
}

私はHttpClientを使って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
        }
    );
}

私の問題は、配列の中の自分の Point の一つでメソッド doSomething を呼び出そうとするときです

var firstPoint = points[0];
firstPoint.doSomething()

コンソールで変なエラーが出ます:

ERROR TypeError: firstPoint.doSomething is not a function

私はTypeScriptとAngularを使い始めてあまり長くないので、コードに何か問題があるのだと思いますが、自分の問題の答えを見つけることができませんでした。何かアドバイスをいただけますか?

回答 #1

問題は、実際にはウェブサービスから Point のインスタンスを取得できないことです。クラスのフィールドを持つ JSON オブジェクトは取得できますが、メソッドは取得できません。クラスをインスタンス化して、Object.assign を使用してオブジェクトリテラルから各 Point インスタンスに値を割り当てることができます。

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

回答 #2

Object.assign()は現在の問題を解決しますが、ネストされたオブジェクトがある場合はそうではありません。その場合、ネストされた各オブジェクトに対してもObject.assign()を行う必要があり、コードベースの複数の場所でこれを行う必要がある場合は面倒になることがあります。

私は代替案を提案します: class-transformer。これを使うと、ネストされたフィールドにアノテーションを付けて、コンパイラにネストされたオブジェクトをどのように作成するかを指示することができます。これにより、トップレベルのオブジェクトをマッピングするためにplainToClass()メソッドを使うだけで、すべての下位フィールドも正しい型/オブジェクトを持つようになります。

例

例えば、私たちには2つのクラスがあるとしましょう:

class Parent {
    name: string;
    child: Child;

    public getText(): string {
        return 'parent text';
    }
}

class Child{
    name: string;

    public getText(): string {
        return 'child text';
    }
}

最初のケースは、すでにうまくいかないことがわかっています:

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

2つ目のケース は 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!

それを機能させるためには、私たちは次のことを行う必要があります:

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

三番目のケース class-transformer を使って:

まず、子クラスのマッピングが定義されるように親クラスを修正します:

class Parent {
    name: string;
    @Type(() => Child)
    child: Child;

    public getText(): string {
        return 'parent text';
    }
}

それから、親オブジェクトにマッピングできます:

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