7

As I'm learning Angular 2 I used an observable to fetch some data via an API. Like this:

getPosts() {
        return this.http.get(this._postsUrl)
            .map(res => <Post[]>res.json())
            .catch(this.handleError);
    }

My post model looks is this:

export class Post {

constructor(
    public title: string,
    public content: string,
    public img: string = 'test') {
}

The problem I'm facing is that the map operator doesn't do anything with the Post model. For example, I tried setting a default value for the img value but in the view post.img displays nothing. I even changed Post[] with an other model (Message[]) and the behaviour doesn't change. Can anybody explain this behaviour?

Jenthe
  • 797
  • 2
  • 12
  • 26
  • What doesn't make sense to me is why Typescript doesn't throw an error when the cast fails or when the generic object is then passed to a function that requires a Post type? Does Typescript type checking just not work? The generic object can have random properties that don't align to the typed parameter so implicit casting can't be happening, yet generic object is passed without issue. – RMuesi Jul 01 '17 at 00:57

2 Answers2

14

I had a similar issue when I wanted to use a computed property in a template.

I found a good solution in this article:

http://chariotsolutions.com/blog/post/angular-2-beta-0-somnambulant-inauguration-lands-small-app-rxjs-typescript/

You create a static method on your model that takes an array of objects and then call that method from the mapping function. In the static method you can then either call the constructor you've already defined or use a copy constructor:

Mapping Method

getPosts() {
  return this.http.get(this._postsUrl)
    .map(res => Post.fromJSONArray(res.json()))
    .catch(this.handleError);
}

Existing Constructor

export class Post {
  // Existing constructor.
  constructor(public title:string, public content:string, public img:string = 'test') {}

  // New static method.
  static fromJSONArray(array: Array<Object>): Post[] {
    return array.map(obj => new Post(obj['title'], obj['content'], obj['img']));
  }
}

Copy Constructor

export class Post {
  title:string;
  content:string;
  img:string;

  // Copy constructor.
  constructor(obj: Object) {
    this.title = obj['title'];
    this.content = obj['content'];
    this.img = obj['img'] || 'test';
  }

  // New static method.
  static fromJSONArray(array: Array<Object>): Post[] {
    return array.map(obj => new Post(obj);
  }
}

If you're using an editor that supports code completion, you can change the type of the obj and array parameters to Post:

export class Post {
  title:string;
  content:string;
  img:string;

  // Copy constructor.
  constructor(obj: Post) {
    this.title = obj.title;
    this.content = obj.content;
    this.img = obj.img || 'test';
  }

  // New static method.
  static fromJSONArray(array: Array<Post>): Post[] {
    return array.map(obj => new Post(obj);
  }
}
The Man Doug
  • 156
  • 1
  • 2
  • Thanks, looks like a decent solution! I'm gonna wait a few more days for maybe some other answers, otherwise I'll mark yours as accepted! – Jenthe Mar 26 '16 at 12:34
  • Good work around. unfortunately requires turning off "noImplicitAny" setting in tsconfig if you wanted it on. – RMuesi Jul 04 '17 at 00:03
0

You can use the as keyword to de-serialize the JSON to your object.

The Angular2 docs have a tutorial that walks you through this. However in short...

Model:

export class Hero {
  id: number;
  name: string;
}

Service:

...
import { Hero } from './hero';

...
get(): Observable<Hero> {
    return this.http
               .get('/myhero.json')
               .map((r: Response) => r.json() as Hero);
}

Component:

get(id: string) {
    this.myService.get()
      .subscribe(
        hero => {
          console.log(hero);
        },
        error => console.log(error)
      );
}
rynop
  • 50,086
  • 26
  • 101
  • 112
  • Does it works if we have different parameter names for class and json data? i.e if class have `name` and we get `user_name` from json. – PaladiN Feb 05 '17 at 06:31
  • 3
    Well, I have to say that it doesn't answer the question at all. Basically, the `as` *syntax* (that you suggested) works **exactly** the same way as `<...>`. *syntax*. The **only** difference is that the `<...>` *syntax* conflicts with (**.tsx** files - `JSX` syntax), as explained in [**this question**](https://stackoverflow.com/questions/33503077/any-difference-between-type-assertions-and-the-newer-as-operator-in-typescript). Also, neither `<...>`, nor `as` will work in this case. The above answer is the correct one. – developer033 May 24 '17 at 22:16