1

Maybe it's better explained with an example.

I have the following class:

export class Foo {
    bar: string;

    doSomething() {
        // whatever
    }
}

And the following provider:

@Injectable()
export class FooProvider {

  constructor(private http: HttpClient) {

  }

  getFoos(): Observable<Foo[]> {
    return this.http.get<Foo[]>('/foos')
  }

}

I was expecting that when subscribing to the Observable, that the data would be an actual array of Foo objects. But to my surprise, the objects inside the array are simple javascript objects, with only data, and no methods (actually when trying to execute doSomething() in any of them a is not a function errors appears.

The only way I've been able to do so is by mapping the observable like this:

@Injectable()
export class FooProvider {

  constructor(private http: HttpClient) {

  }

  getFoos(): Observable<Foo[]> {
    return this.http.get<Foo[]>('/foos')
        .map(foos => {
            foos.map(foo => {
                let f = new Foo();
                Object.assign(f, foo);
                return f;
            };
        });
  }

}

The thing is, that I don't want to have to do so in every method of every provider. Seeing that the code to do the conversion is quite simple, I'm guessing that I'm missing something, but I'm unable to find so in the docs.

Could anyone help me with this?

Alfergon
  • 5,463
  • 6
  • 36
  • 56
  • 1
    you're not missing anything, you have to do the `new Foo(someData)` manually. Type information is not kept at runtime thus typescript won't do anything to convert your data either. – toskv Jan 17 '18 at 18:29

1 Answers1

2

Just like @toskv says, you'd need to create the object manually. Even though both of the answers tagged as duplicated could solve the OP, I'd like to add this answer because I usually do things a little bit different.

All the answers use Object.assign(f, foo); to copy the properties from the plain object to the class instance. That's ok, but in my case I like to have more control over the name of the properties used in the app. Why? So if the api returns a property username but then it is changed and the name of the same property is fullname, I don't want to update all the pages from the app where that property is being used.

So because of that I usually create some static methods, like this:

export class Foo {
    bar: string;

    doSomething() {
        // whatever
    }

    // Static method that creates the instance from 
    // a plain javascript object
    public static fromObject(object: any): Foo {
        let instance = new Foo();

        if(!object) return instance;

        // Here you can decide which name should each property have. 
        // If the API changes in the future, you would only need to 
        // modify a single line of code and that's it. You could also
        // modify each property and give it a specific format if you want
        instance.bar = object.bar;

        // more properties ...

        return instance;
    }

    // Static method that creates a list of instances from a list 
    // of plain javascript objects
    public static fromJsonList(objectList: Array<any>): Array<Foo> {
        let instances = new Array<Foo>();

        if (!objectList || (objectList && objectList.length === 0)) return Foo;

        objectList.forEach(object => instances.push(Foo.fromJson(object)));

        return instances;
    }
}

And then, I use it in the service:

getFoos(): Observable<Foo[]> {
    return this.http.get<Foo[]>('/foos').map(response => Foo.fromJsonList(response));
}

So by doing things like this:

  1. If the name of any property changes, you'd need to change a single line of code and every page will still work properly.
  2. All the "logic" related to how each instance should be created is encapsulated in the model, instead of adding part of it in the service.
sebaferreras
  • 44,206
  • 11
  • 116
  • 134
  • 1
    Although I like your answer, I'm closing the question as it actually is answered on the other questions. Thanks anyway ;) – Alfergon Jan 18 '18 at 08:09