3

While trying to find out what would be the nicest way to convert the json response of an http call to a TypeScript class/interface I found this code-snippit on stack overflow.

getTeachers(): Observable<Teacher> {
  return this.http.get('someUrl')
    .map((res: Response) => res.json())
}

It looks nice, but I don't understand how it works.

  • Why is there no cast-syntax to convert the generic any type of res.json() to the more specific type Teacher?
  • Why is it recommended for Teacher to be a TypeScript interface and not a TypeScript class?
Community
  • 1
  • 1
Tom Deseyn
  • 1,735
  • 3
  • 17
  • 29

2 Answers2

0

If you look at the signature of the Observable:

export declare class Observable<T> implements Subscribable<T> {...}

you can see that it requires argument T. SomeThing<SomeType> is called a generic and to quote TypeScript Hadnbook, if you look at generic identity function:

  function identity(arg: any): any {
    return arg;
  }

While using any is certainly generic in that will accept any and all types for the type of arg, we actually are losing the information about what that type was when the function returns. If we passed in a number, the only information we have is that any type could be returned.

Instead, we need a way of capturing the type of the argument in such a way that we can also use it to denote what is being returned. Here, we will use a type variable, a special kind of variable that works on types rather than values.

  function identity<T>(arg: T): T {
    return arg;
  }

You can define getTeachers(): any {} in case you don't care about what this function returns (or if you don't know). It will work, but suppose few months from now you are upgrading your app, you make a nice shinny new component and it needs to call getTeachers(). What will it return? Any. Cool. =digs-through-months-old-code= Repeat N times (;

But since you know what function does (returns http.method, which is an Observable) you can write getTeachers(): Observable<any>{}. ...shinny new component; call getTeachers(). What will it return? Observable. Nice, I can subscribe to it, and get Any. OK, I can work with that...

In most cases/examples , that's enough. But you can be even more specific. If you know what this.http.get('someUrl') returns (let's say it's an JSON object in the body of the response, with header 'Content-Type' set to 'application/json' - and you use Respone.json() to extract it), you can write getTeachers(): Observable<{}>{}. Now we know it's an Object.

Finally, if you know that object will have same properties as interface Teacher, you can define your function like this:

  getTeachers(): Observable<Teacher> {}

Why is there no cast-syntax to convert the generic any type of res.json() to the more specific type Teacher?

There's no conversion here, data stays the same. You are just providing "description" of that data by declaring it to be of specific type. Based on the information you know about it. No magic (:.

Why is it recommended for Teacher to be a TypeScript interface and not a TypeScript class?

In this context Teacher can be either class or an interface. Here it doesn't matter, TypeScript will read information from either class or interface. If you won't use Teacher in any other context you make an interface (it doesn't compile to JavaScript, so less code). If you use it somewhere else, for example if you need an instance of Teacher:

  let new_guy = new Teacher();

then you should declare Teacher as a class.

Sasxa
  • 40,334
  • 16
  • 88
  • 102
  • Since `Observable` defines the generic argument as `Teacher`, does the `.map` function infer the mapping to map the json result as `typeof(Teacher)`? – Metro Smurf Jun 20 '16 at 14:29
  • @Sasxa Even when the data stays the same, in most languages you need to explicitly cast when converting a generic type (like any) to a more specific type (like Teacher). – Tom Deseyn Jun 20 '16 at 17:16
  • @TomDeseyn that is true of languages that have `real` static typing. Typescript is a bit different, see [here](https://medium.com/@gaperton/typescript-static-or-dynamic-64bceb50b93e) for a more in depth explanation of the type system concept – Mord Zuber Jun 20 '16 at 20:09
0

I'll answer my own question based on @Sasxa's answer and input I received via other channels. My focus is explaining it to people which have a background working with strongly typed languages.

any

At first, you may asume any is the super type of all types. And that's the case! Any can be used to refer any instance.

The TypeScript spec also defines any to be assignable TO all types. This is by definition and is the rationale is probably javascript interop. From the spec:

"The Any type is a supertype of all types, and is assignable to and from all types."

any as a generic parameter

Statically typed languages also have the concept of 'Covariance' and 'Contravariance'. When the generic parameter is used as an output type, it can be substituted for a base type (covariance). For example: 'iterator of string' can be assigned to the 'iterator of object'. When the generic parameter is used as an input type it can be substituted for a derived type (contravariance).

Because any is assignable from and to all types, the same is true for parameterized types. Observable<Teacher> can thus convert from and to Observable<any>.

type assertion

TypeScript type assertion can be used to change the perceived type of an instance. Since TypeScript is a structurally typed language, the type describes the structure of the instance but the instance itself is unaware of the type (used in the assertion). When asserting the type we should't use a type that has other members than that of the instance. In the case of res.json() for example, we don't expect the type to have method members since it is data retrieved via an http get call. It doesn't matter whether the type is a class or an interface.

Tom Deseyn
  • 1,735
  • 3
  • 17
  • 29