Tuesday, May 17, 2016

TypeScripting AngularJS 1.x - using $http

This is a continuation of older posts in TypeScripting AngularJS 1.x series. Here lets see, how we can use the $http service in typed way using TypeScript.

The API

The main TypeScript type, we need to understand here, is the ng.IHttpService. This is the equivalent type of $http object. This comes via the AngularJS typedefinition file which can be downloaded from nuget. The another type is the ng.IHttpPromiseCallbackArg<T>. This is the container which is returned via the Then() method.

Sample

Before coding the client side, lets see the server side code which is going to be called from browser.
public class HomeController : Controller
{
    public ActionResult GetCountryMetaData(string id)
    {
        RegionInfo rInfo = new RegionInfo(id);
        return Json(new
        {
            DisplayName = rInfo.DisplayName,
            Currency = rInfo.CurrencyEnglishName,
            CurrencySymbol=rInfo.CurrencySymbol
        },JsonRequestBehavior.AllowGet);
    }
}

This action method takes id as string and return the country / region details. Anonymous return type has 3 properties which are all strings.
If we host this inside IIS using web application named testapi, we can invoke the API by browsing the below url.

http://localhost/testapi/home/getcountrymetadata/nz
Result will be - 
{"DisplayName":"New Zealand","Currency":"New Zealand Dollar","CurrencySymbol":"$"}

Let's see how this can be invoked from TypeScript without using 'Any' type.
export class CountryController {
        testprop: string;
        emp: Country;
        http: ng.IHttpService;
        Countries: Array<Country> = new Array<Country>();
 
        constructor($scope: any,
            $http: ng.IHttpService,
            this.http = $http;
            $scope.Countries = this.Countries;
        }
        getForChina() {
            this.http
                .get('http://localhost/testapi/home/getcountrymetadata/cn')
                .then((result: ng.IHttpPromiseCallbackArg<Country>) => {
                    this.Countries.push(result.data);
                });
        }
}
class Country {
    DisplayName: string;
    Currency: string;
    CurrencySymbol: string;
    constructor() {
 
    }
}

Casting in TypeScript

The first confusion will happen on the casting of http result to Country object. Where we are telling the return type is Country in TypeScript?

Here is the difference between the real type safe languages such as C# / Java and TypeScript. In TypeScript since its finally running as JavaScript, we can accept values in any type, if that looks similar to the incoming object. Here the output of web request has properties which are matching to properties of Country class so those are just copied. If there are no matching properties still this code compiles but at run-time the properties of result object will be undefined. No exception in any case.

We can make it more TypeSafe by adding generic type to the get method.
this.http
    .get<Country>('http://localhost/testapi/home/getcountrymetadata/cn')
    .then((result: ng.IHttpPromiseCallbackArg<Country>) => {
                    this.Countries.push(result.data);
    });

Passing typed parameters to $http.get()

The get() accepts an optional ng.IRequestShortcutConfig object too. Lets see how that can be passed into the method in type safe way.

var config: ng.IRequestShortcutConfig = {};
            config.cache = true;
            this.http
                .get<Country>('http://localhost/testapi/home/getcountrymetadata/cn',config)
                .then((result: ng.IHttpPromiseCallbackArg<Country>) => {
                    this.Countries.push(result.data);
                });

Interesting thing here in TypeScript is, we don't need to create the implementation class to use the interface. This again is feasible, since it runs in JavaScript engine where there is no type safety. After the compilation everything is JavaScript. We know that, there is nothing called interface in JavaScript runtime.

The {} creates an object just like in JavaScript. After that we can set required properties. Since we have declared the variable as ng.IRequestShortcutConfig, we will get intellisense on the config object. We don't need to remember all the properties, which we need to do in pure JavaScript programming.

Here config.cache.= true tells the $http, to cache the requests using Angular JS cache factory.

Happy TypeScripting...

No comments: