11

Object returned by $http.get has no methods. Example: I have my class model

export class Lab {

    constructor(
        public id: number,
        public name: string,
        public description: string,
        public isActive: boolean,
        public classes: Classes[]
    ) { }

    isActive(lab: Lab) {
        return this.isActive;
    }
}

in my service I call http fetching lab

getLab(labId: number) {
    return this.http.get<Lab>(DidacticsServiceUrls.apiRoot + labId).toPromise();
}

and when I get this in some component, method isActive is undefined, so call

lab.isActive();

throws exception. Is there any clean solution for this?

kriss
  • 976
  • 17
  • 27
  • 1
    Typing the response object **does not** do any casting or conversion. It's just a hint to the compiler. If you want to convert the plain object you get from the JSON, *you need to write that code*. – jonrsharpe Jan 04 '18 at 20:10
  • 2
    Possible duplicate of [Angular: Typescript casting JSON response as object model not working](https://stackoverflow.com/questions/46022440/angular-typescript-casting-json-response-as-object-model-not-working) – jonrsharpe Jan 04 '18 at 20:14
  • Or https://stackoverflow.com/questions/46839214/httpclient-type-safety-seems-to-ignore-interface, or https://stackoverflow.com/questions/45145385/angular-2-typescript-cast-json-to-object, or ... – jonrsharpe Jan 04 '18 at 20:15
  • 1
    It's not duplicated, this problem is about calling method after a return from backend. This question was really useful. – Guilherme Alencar Jan 25 '19 at 13:22

4 Answers4

15

The server just returns data formed with properties from the defined object. It doesn't actually create an instance of the object.

Try something like this:

this.lab = Object.assign(new Lab(), this.retrievedLab)

Where this.retrievedLab is the data returned from the server.

This should create the object and then copy any of the retrieved properties into it.

DeborahK
  • 57,520
  • 12
  • 104
  • 129
2

In your get call service you can do as @Deborahk mentioned

getLab(labId: number) {
return this.http.get<Lab>(DidacticsServiceUrls.apiRoot + labId)
           .map(res => Object.assign(new Lab(), res))
           .toPromise();
 }
fastAsTortoise
  • 631
  • 6
  • 17
1

You can also have a class and extend it like this:

getLab(labId: number) {
    return this.http.get<Lab>(DidacticsServiceUrls.apiRoot + labId)
    .pipe(Lab.serializeResponseMap())
    .toPromise();
}

class definition:

export class Lab extends SerializableMap {
    static instanceType = Lab;

    constructor(
        public id: number,
        public name: string,
        public description: string,
        public isActive: boolean,
        public classes: Classes[]
    ) { super(); }

    isActive(lab: Lab) {
        return this.isActive;
    }
}


class SerializableMap {
  static instanceType: any;

  static serializeResponseMap(): any {
    const createInstance = (r) => {
      return Object.assign(new this.instanceType(), r);
    };

    return map((respValue: any) => {
      if (Array.isArray(respValue)) {
        return respValue.map(r => createInstance(r));
      }
      return createInstance(respValue);
    });
  }
}
Matjaz Hirsman
  • 326
  • 3
  • 9
1

This version is inspired by @Matjaz Hirsman response (thanks!), with added deep cloning.
Also: it resembles the Decorator pattern more than Serialization (or actually Deserialization).

getLab(labId: number) {
    return this.http.get<Lab>(DidacticsServiceUrls.apiRoot + labId)
    .pipe(Lab.decoratingMap())
    .toPromise();
}

Classes:

export class Lab extends Decorable {
    static instanceType = Lab;

    constructor(
        public id: number,
        public name: string,
        public description: string,
        public isActive: boolean,
        public classes: Classes[]
    ) { super(); }

    isActive(lab: Lab) {
        return this.isActive;
    }
}


class Decorable {
  static instanceType: any;

  /**
   * Convert single entity into fully-fledged object
   * @param source js object
   * @return fully-fledged HalResource
   */
  static decorateSingle(source: any) {
    const target = new this.instanceType();
    for (const key in target) {
      if (source[key]) {
        if (target[key] && typeof target[key] === 'object') {
          target[key] = Object.assign(target[key], source[key]);
        } else {
          target[key] = source[key];
        }
      }
    }
    return target;
  };

  /**
   * Convert entity or array of entities into fully-fledged objects
   * @param response js object (or objects)
   * @return fully-fledged object (or objects)
   */
  static decorate(response: any) {
    if (Array.isArray(response)) {
      return response.map(element => this.decorateSingle(element))
    } else {
      return this.decorateSingle(response);
    }
  }

  /**
   * Rx Map operator decorating the JS objects into fully-fledged objects
   */
  static decoratingMap() {
    return map((response: any) => this.decorate(response));
  }
}
ppi
  • 86
  • 7