Angular & TypeScript: Typed interaction with your WebAPI-backend

with 1 Comment

I’ve written previously about angulars $http/$resource and about typescript codegeneration, now its time to write a few words about my current project and how we used TypeWriterand $http to get a completely typed front-end development.

First a few words about our setup. We are using WebAPI to serve our angularjs-app with JSON. Our controllers are returning Task of some model or just Task, something like this:

 

Our angular-app does not know about either OrganizationUsersController or the models it’s returning.

Lets set up some goals:

  • Generate TypeScript Interfaces matching C#-models.
  • Generate TypeScript Classes matching our WebApi-controllers.
    • Taking the same parameters
    • Returning the correct type
    • Constructing a correct url
  • Changing/Adding/Removing a property should be automaticaly reflected in our generated code.

Alright, lets get to it!

Install TypeWriter

You can find TypeWriter here: http://frhagn.github.io/Typewriter/

The current version when writing this is 1.0.0beta, but only 0.9.13 is on VisualStudio-gallery. This tutorial uses 1.0.0beta so download the repository from github if its not on VisualStudio-gallery yet.

Generate TypeScript Interfaces matching C#-models

I’ve placed all my models (and only my models) in the App.Models-namespace.

Add a new .tst-file (Right Click a folder -> Add -> New Item.. -> TypeScript Template file), call it Models.tst. Make it look like this:

This will take all classes where it’s fullname starts with App.Models and iterate over them (the [ ...] syntax). For each class we’ll create a interface, and for each of that class’s properties we’ll create that property on our interface. As simle as that.

Save the file, and poof: your models will apear nested under your Models.tst-file! As simple as that!

You have inheritance in your Models? Alright, lets get that sorted.

Inheritance syntax for TypeScript is like this: interface SomeModel extends SomeBaseModel { ... }. Lets adjust our export interface.. line to this: export interface $Name $BaseClass[extends $Name] {. Its simple realy, whats inside [...] is only written if there is a BaseClass.

Super, goal 1 achieved!

Generate TypeScript Classes matching our WebApi-controllers

This is a bit more complex, but we’ll take it one step at the time.

Add a new .tst-file, called Controllers.tst:

This is abit more complex. At the top we’re creating something call extensions, C#-code that we can use in our template. Our angular-service probably cant be called *Controller, so we’re replacing “Controller” with “Service”. We’re also extracting the http-verb the method is using by looking at its attributes.

Otherwise this template resembles Models.tst, except for the generic argument to post: $Type[$IsGeneric[$GenericTypeArguments][<void>]]. This might read as: given the return $Type of this $Method, if it’s Generic (like Task in our controller) get the GenericTypeArguments, if not: say the type is void.

The output for our Controller:

Close! We’ve achived 2 of our 3 goals for Controllers:

  • Taking the same parameters: done!
  • Returning the correct type: done!

## Constructing a correct url

This is harder than it might look like. Lets take the Update-method as an example.

Our generated method will PUT to api/organizationusers/{userId:Guid} with a body consisting of all our $Parameters.

We would like it to PUT to api/organizationusers/${userId}?rowVersion=${rowVersion} (using TypeScript string interpolation) with viewModel as body and our rowVersion as a query-string. Hard, but not impossible. Lets use the power of C#-extensions.

Add these lines above our ServiceName-method:

And change $Route to $AdjustedRoute in our template. The code will find parameters in the $Route-string and replace them with TypeScript string interpolation, voila:

Better, but still it lacks in when and what it’s supposed to have as a body. Add two more methods to our extensions:

and change our this.$http...-line to this: return this.$http.$Verb<$Type[$IsGeneric[$GenericTypeArguments[$Prefix$Name]][void]]>($AdjustedRoute$RequiresData[, $DataParameter]);.

This will only take one parameter as body, and the correctly. Our output now looks like this:

The last piece is missing, our querystring. Add the last two methods to our extensions:

and adjust our this.$http..-line: return this.$http.$Verb$Type[$IsGeneric[$GenericTypeArguments][<void>]]($AdjustedRoute$RequiresData[, $DataParameter]$HasParams[, { params: { $Params[$name: $name][, ] } }]);

If we have any params, parameters that have the [FromUri]-attribute or IsPrimitive (int, string, enum etc) and does not exist in the route, then use $http‘s options to create a nice querystring. Output:

Alright, high five!

A typed front-end developer experience, reflecting changes in our backend, without us having to write a line. Build errors if removing or changing backend without changing our front-end code. Awesomeness.

  • Generate TypeScript Interfaces matching C#-models: done!
  • Generate TypeScript Classes matching our WebApi-controllers: done!
    • Taking the same parameters: done!
    • Returning the correct type: done!
    • Constructing a correct url: done!
  • Changing/Adding/Removing a property should be automaticaly reflected in our generated code: done!

 

If you are using TypeScript and MVC/WebAPI you will surely benefit from using TypeWriter to make your Front- and Back-end less seperate, I hope you will give it a try!

Lastly, our ending controller-template in full:

 

One Response

  1. […] Edit: And here is a later blogpost, when you are ready to take this to the next level: Angular & TypeScript: Typed interaction with your WebAPI-backend […]

Leave a Reply