26

I have this simple Component

import {Component} from 'angular2/core';
import {RouterLink, RouteParams} from 'angular2/router';
import {Http, Response, Headers} from 'angular2/http';
import {User} from '../../models/user';
import 'rxjs/add/operator/map';


@Component({
    template: `
        <h1>{{user.name}} {{user.surname}}</h1>
    `,
    directives: [RouterLink],
})
export class UserComponent {

    user: User;

    constructor(private routeParams: RouteParams,
        public http: Http) {
            this.user = new User();
            this.http.get('http://localhost:3000/user/' + this.routeParams.get('id'))
                .map((res: Response) => res.json())
                .subscribe((user: User) => this.user = user);
        console.log(this.user);
    }
}

Why does subscribe not cast the response into a full User object. When I am logging the user variable my console say User {_id: undefined, name: undefined, surname: undefined, email: undefined}. But nevertheless binding to .name and .surname in the view is working..

What happens here? Where is the user actually stored?

Daniel Hitzel
  • 1,342
  • 2
  • 12
  • 27
  • Possible duplicate of [How do I cast a JSON object to a typescript class](http://stackoverflow.com/questions/22875636/how-do-i-cast-a-json-object-to-a-typescript-class) – Günter Zöchbauer Mar 15 '16 at 14:34
  • I read this is a good article also https://stackoverflow.com/questions/63964124/angular-convert-api-data-into-new-data-type-in-reusable-clean-method?noredirect=1&lq=1 –  Sep 20 '20 at 09:33

3 Answers3

25

Good practice is to consume data from GET response using

Observable<Model>

(regarding to Angular documentation https://angular.io/guide/http) so...

// imports

import {HttpClient} from "@angular/common/http";

// in constructor parameter list

private http: HttpClient

// service method

getUser(): Observable<User> {return this.http.get<User>({url}, {options});}

You do not need to do anything more. I consider this approach as most friendly.

Przemek Struciński
  • 4,990
  • 1
  • 28
  • 20
  • 49
    This will work for most approaches, but it doesn't cast, more like a type assertion. If you have a function in your class `export class User { test() { console.log("works"); } }` You can't run user.test(). – ovmcit5eoivc4 Oct 27 '17 at 07:33
  • 5
    Just to avoid confusion for anyone this approach is only available in Angular 4.3+ where the new HttpClient was introduced. – Darren Lewis Nov 11 '17 at 16:46
  • 1
    @DarrenLewis , what is approach we have to use if get method wants to return array of Model? – Anil Mar 24 '18 at 06:54
  • @Anil This method works fine also for an array of Model. Just instead of User you put User[] – Przemek Struciński Apr 13 '18 at 10:47
  • @ovmcit5eoivc4 No, this is not how TypeScript works. There are no runtime type checks. Most devs seem to be not even aware that their type annotated variables aren't necessarily instances of the class. For example the `hero` object in Angular's tutorial: https://angular.io/tutorial/toh-pt1#create-a-hero-class. – fishbone Aug 08 '18 at 08:21
  • 2
    The author's problem is that his `log` command is executed before the response arrives. Your answer isn't related to the author's problem at all in my opionion. Furthermore he asks for a "full `User` object" which I interpret as `User` class instance. So even if his log was at the correct possition your answer would be wrong as it will return a plain object (as ovmcit5eoivc4 pointed out above) – fishbone Aug 08 '18 at 08:25
  • I logged in just to vote up your answer because it really helped me. – Muhammad Faizan Jan 23 '19 at 13:43
  • This casts the any object (from the response) to the Users class. This results in a Partial object, which just blindly maps the properties. This isn't a full object, so this is not the answer to this question. None of the methods of the User class will work, including getters/setters, the constructor and all other methods, since they don't exist on Partial – ZolaKt Jul 02 '19 at 11:52
22

Found a solution here: https://stackoverflow.com/a/29759472/2854890

My Method now looks like this:

constructor(private routeParams: RouteParams,
    public http: Http) {
    this.user = new User();
    this.http.get('http://localhost:3000/user/' + this.routeParams.get('id'))
        .map((res: Response) => res.json())
        .subscribe((json: Object) => {
            this.user = new User().fromJSON(json);
        });
}

I enhanced the Serializable by returning the object in the end, so I can leave out something like

var u = new User();
u.fromJSON(...);

and just write

new User().fromJSON(json);

Serializable class

export class Serializable {

    fromJSON(json) {
        for (var propName in json)
            this[propName] = json[propName];
        return this;
    }

}
Derlin
  • 9,572
  • 2
  • 32
  • 53
Daniel Hitzel
  • 1,342
  • 2
  • 12
  • 27
  • 1
    In Angular 6 that map() line is giving error: `Argument of type '(res: Response) => Promise' is not assignable to parameter of type '(value: Response, index: number) => Promise'.` – Kristopher Windsor Jun 27 '18 at 05:42
  • map has changed in Angular 6. Check this out: https://academind.com/learn/angular/snippets/angular-6-whats-new-angular-upgrade/#rxjs-6-changes-operator-usage – Daniel Hitzel Jul 03 '18 at 11:13
  • 1
    @DanielHitzel Your original problem was that your `console.log` is executed before the response arrived. Even if it was at the correct position, you would overwrite the value in your `subscribe` handler by a plain object (`this.user = user`) which is not an instance of `User` anymore (Nevertheless a `User`-compatible object with same properties). Both problems are solved by this answer. However, I wanted to point out that your actual problem - property values are `undefined` - originated in another mistake. – fishbone Aug 08 '18 at 08:37
  • looking at the code with more experience, yes that's seems to be true :D – Daniel Hitzel Mar 26 '19 at 16:41
0

That's not supported by TypeScript.

See How do I cast a JSON object to a typescript class for more details.

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • But why am I able to bind to the data anyway. It gets displayed, whereas the variable I am binding to is logged as undefined.. How does binding work then – Daniel Hitzel Mar 15 '16 at 14:37
  • JSON and object is interchangeable in TypeScript/JS but the created object will only contain what the JSON contained, but no methods or other fields that are only defined in the prototype of of the real class. – Günter Zöchbauer Mar 15 '16 at 14:38
  • if I change this to `.subscribe(data => this.user = new User(data._id, data.name, data.surname, data.email));` the log is still the same. What do I need to log in order to get the object the view in binding to? – Daniel Hitzel Mar 15 '16 at 14:47
  • There is another mistake I missed. ` console.log(this.user);` is executed before the HTTP request is made. `this.http.get()` is async, which means the task is enqueued in the browser event queue for later execution, then JS execution is continued (with your log command). When the HTTP call is done, the callback passed to `.subscribe(...)` is executed, but that's much later. Move the log call into `subscribe((user: User) => { ... });` – Günter Zöchbauer Mar 15 '16 at 14:52