Angular with typescript architecture

Bear with me, this is going to be a long post.

For the past several months I’ve been working on a production single page application written with AngularJS and TypeScript, and I wanted to share how myself and my team have architected the application. In case you were wondering, the app was written using typescript 0.8.3 and not 0.9.1 (which is out now with generics).

In general, I was really unhappy with a lot of the AngularJS examples I had found on the internet when researching how to structure the application, since they all looked flimsy and poorly constructed. While the examples found online were easy to read and follow, they clearly wouldn’t work with an actual large application.

I had several goals:

I didn’t want to have to constantly register directives/filters/controllers/etc with Angular everytime I added something

I didn’t want to have to update my main index.html page with any references to new files as I worked on them

I wanted to avoid string typing as much as possible by centralizing all string references to angular components

I wanted everything testable

I didn’t want to inline directive html templates, I wanted them in in separate html components

I wanted one file for each class

I wanted everything strongly typed

Anything less than these requirements, I felt, would compromise maintainability and extensibility for the future, especially if the app had more than one developer working on it.

css – All scss files and the final compiled app.css which is what is linked to from index.html

img – All statically served image files

js – All typescript files broken down into individually subsections. Also the definitions folder def will be seperated out from any local custom definitions if necessary, and all vendor definitions (such as angular, rxjs, jquery, etc). Also, app.ts (which will be discussed later) is the main app bootloader. _all.d.ts is an aggregate typescript def file that has all the references to the .ts files in the application

Wrapping Angular in OO

There’s no reason you can’t wrap the angularJS object initializers that are used to define directives, filters, controllers, etc, into strongly typed local classes. My team and I built a bunch of templates to use with IntelliJ Idea which made creating new directives/controllers/filters/services/etc extremely easy.

There are a few things going on here. First, there is a single aggregate definitions file that is always included. This means we don’t need to pollute each file with definitions. The _all.d.ts is also auto generated using a dependency builder we wrote (posted on my github). It finds all .d.ts files based on a dependency configuration and auto populates the all.d.ts for you.

Second, notice that the controller scope is typed with an interface. By making sure we use an interface we can guarantee we won’t try to access a field in the UI that we didn’t explicity expose.

Third, since for controllers I’m using the $inject annotation, we’re making sure to not hardcode any angular strings. Everything is centralized in an AngularGlobal class:

Also, you’ll notice that in the constructor of the controller there is a call to the base class with the inject arguments. The base class is going to validate that the arguments passed to the controller match the items in the $inject array. This way we can get fail fast runtime errors if we ask to inject something, but didn’t wire up the constructor right:

Disregarding all the wrapping for namespaces, you can see that the constructor for Test is just a function that takes a $scope. So, it’s the exact same thing. Now you can wire up your routes in such a way to pair partials with controllers like this:

Auto registration of services, directives, filters, and models

For our purposes, we did something slightly different with services, models, directives and filters. The reason being that controllers are always “registered” with angular via the routing. However, services, models, directives, and filters are usually registered with separate angular modules that the main app depends on. In this scenario, since it’s common to create lots of directives, etc, we didn’t want to have to manually register anything.

Here there is a static ID which is the name of the model, and a static injection function that returns the array notation for injection. In the array notation the last line is the function that gets called by angular with the relevant injectable types. We preferred array notation vs $inject notation since array notation is safe to minimize. The name httpService at this point doesn’t matter, since we asked angular for it using the static AngularGlobal class discussed above.

The ID and injection functions are important, and I’ll show why in a second.

[ts]
/***
* We are simulating doing something like this:
*
* this.directives.directive(directives.DynamicView.ID, directives.DynamicView.injection());
*
* The "this.directives.directive" is the function we want to call on which is the registration function
*
* Since the ID and injection function are statically defined in our classes and MUST be defined for
* our angular injection to work, we can type them temporarily here in this function
*
* This way if we add new items to any namespace that have an injection function and an ID we will
* automatically register them to the right angular module *
*
* @param namespace
* @param registrator
* @param byPass
*/
wire(namespace:any, registrator:(string, Function) => ng.IModule , byPass?:(s) => bool){
for(var key in namespace){
try{
if(byPass != null && byPass(key)) {
continue;
}

Since namespaces in javascript are just objects with properties, we can make an assumption that anything under the devshorts.app.models namespace is a model if it has a static ID and a static injection function. If it does, then we can register that class with the correct angular module.

Now we never have to worry about wiring up directives, filters, models, or services since at runtime they are auto wired for us. This gives application development a more native feel, just by adding the file means it exists and is available for injection.

Merging the files

I’ve read about people promoting writing everything in javascript into one file, since they don’t want to have the overhead of loading hundreds of .js files on load. I vehemently disagree here. From a development standpoint you should have one class per file. It makes it easier to split up your code, move things around, and to properly organize your application. However, in a production environment you aboslutely should distribute a single merged file. But that’s trivial. I linked to it earlier on, but the dependency tool I had also populates index.html with the appropriate script references for all .js files it finds (that are configured for it to find). If you are interested, go check out the typescript dependency builder (which I will probably blog about again later).

Assuming that your index.html page has all the relevant js files in the head, then it’s easy to write a simple script to merge all the files into one and serve that up when the application is loaded in production. In debug mode, you can serve up all the independent files, it’s just a matter of toggling your node config or your web.config (in an asp.net application).

As an example, here is an F#/C# MSBuild task class to do that for you:

Conclusion

Organizing an application is hard, and everyone has their own style, but proper application organization and architecture is critical to being able to scale your codebase. Also, sometimes to make the development experience pleasant, you need to invest the time to build tools to automate boring tasks for you. Until we spent the time to create the dependency tools, working with angular and typescript was a real headache, but now it’s an absolute joy.

There are more things I haven’t covered, such as how we dealt with filters, localization, model and service aggregation (for easy injection), and http interceptors but I’ll save those for another post.

Yes and no. Doing it this way you are relying on naming, so angular knows that $http is the http service. This is a bad idea cause you can’t minimize your code. Instead do something like this (forgive any typos, I’m in tokyo right now typing this on my phone:

[AngularGlobal.HTTP,
h => new Test(h)]

Where AngularGlobal could be a static class and the value HTTP maps the angular id of the http service (the actual value I think is string “$http”). The variable “h” will be given the injected http service. I intentionally named it a single letter to demonstrate that using this method the naming convention no longer matters and is now safe for minimization.

If you have more questions let me know, I am back next week and will have time to post more detailed examples in gists or on my github.

Now it may be clearer what the static “ID” value is on models/directives/services, etc that I defined. You reference the string by the static class so this way you centralize all strings into one location. each model, whatever, will define its own dependencies.

By defining the dependencies and the final construction function in static properties you can auto wire them up with angular if you follow a specific convention (I used ID and “injection”)

Hello, Anton. I have one more question, not related to this article but to some thing i’m currently working on. May be You know how to register in angular dynamically loaded(via requireJS for an instance) controllers, directives, services, etc?
Thank You.

Wonderful post you have created here! I am currently fiddling with your bits, and I wonder what the best way would be to correctly inject the underscope dependency into ControllerBase. There is an UnderscoreStatic interface in the underscore.d.ts type declaration. So now I have my controller requesting an object with this interface so it can be passed towards the ControllerBase class. For the implementation I have found the angular-module underscore-angularized with the same bower-package name. One should really prevent polluting the global namespace :)