If you plan to build more than a playground with Angular 2, and you obviously do since you are interested in patterns for performances, stability, and operations, you will most likely consume an API to feed your application. Chances are, this API will communicate with you using JSON.
Let's assume that we have a User class with two private variables: lastName:string and firstName:string. In addition, this simple class proposes the hello method, which prints Hi I am, this.firstName, this.lastName:
class User{
constructor(private lastName:string, private firstName:string){
}
hello(){
console.log("Hi I am", this.firstName, this.lastName);
}
}
Now, consider that we receive users through a JSON API. Most likely, it'll look something like [{"lastName":"Nayrolles","firstName":"Mathieu"}...]. With the following snippet, we can create a User:
let userFromJSONAPI: User = JSON.parse('[{"lastName":"Nayrolles","firstName":"Mathieu"}]')[0];
So far, the TypeScript compiler doesn't complain, and it executes smoothly. It works because the parse method returns any (that is, the TypeScript equivalent of the Java object). Sure enough, we can convert any into User. However, the following userFromJSONAPI.hello(); will yield:
json.ts:19
userFromJSONAPI.hello();
^
TypeError: userFromUJSONAPI.hello is not a function
at Object.<anonymous> (json.ts:19:18)
at Module._compile (module.js:541:32)
at Object.loader (/usr/lib/node_modules/ts-node/src/ts-node.ts:225:14)
at Module.load (module.js:458:32)
at tryModuleLoad (module.js:417:12)
at Function.Module._load (module.js:409:3)
at Function.Module.runMain (module.js:575:10)
at Object.<anonymous> (/usr/lib/node_modules/ts-node/src/bin/ts-node.ts:110:12)
at Module._compile (module.js:541:32)
at Object.Module._extensions..js (module.js:550:10)
Why? Well, the left-hand side of the assignation is defined as User, sure, but it'll be erased when we transpile it to JavaScript. The type-safe TypeScript way to do it is:
let validUser = JSON.parse('[{"lastName":"Nayrolles","firstName":"Mathieu"}]')
.map((json: any):User => {
return new User(json.lastName, json.firstName);
})[0];
Interestingly enough, the typeof function won't help you either. In both cases, it'll display Object instead of User, as the very concept of User doesn't exist in JavaScript.
This type of fetch/map/new can rapidly become tedious as the parameter list grows. You can use the factory pattern which we'll see in Chapter 3, Classical Patterns, or create an instance loader, such as:
class InstanceLoader {
static getInstance<T>(context: Object, name: string, rawJson:any): T {
var instance:T = Object.create(context[name].prototype);
for(var attr in instance){
instance[attr] = rawJson[attr];
console.log(attr);
}
return <T>instance;
}
}
InstanceLoader.getInstance<User>(this, 'User', JSON.parse('[{"lastName":"Nayrolles","firstName":"Mathieu"}]')[0])
InstanceLoader will only work when used inside an HTML page, as it depends on the window variable. If you try to execute it using ts-node, you'll get the following error:
ReferenceError: window is not defined