Combining the best of two worlds.
TypeScript offers an object-oriented architecture experience, which means all code is defined in classes, interfaces and most of those classes can be instantiated into objects. It also supports encapsulation, which protects the data from unintended access and modification.
Learning path of AngularTS
If you’re no stranger to AngularJS you will notice that the structure remains the same. Two way data binding, controllers, services, … But be aware that it has a different syntax in TypeScript. I will show you the different best practices to implement such components.
TypeScript Definition Files
When using TS we will refer to TSD files. These files describe the types defined in external libraries such as Angular. To install the Angular TSD files we use typings. To use the typings manager we install it with:
Afterwards install Angular with:
--ambient --save enables the flag and persists the selection in ‘typings.json’
All the installed TSD files are gathered in the typings folder. In the main.d.ts file you will see the references the application will use for Angular. Since Angular has multiple libraries, you can use the search command to find the required definition.
It is possible that you have to declare the reference on top of your file.
Modules are here to help us modularize our code. It is a best practice to use one main module as the root of your application. Multiple modules are being used for third-party libraries or common code. To let the module know the existence of every component, they have to register themselves. Below every component declaration you will see a registration to the module. When registering the module you have to add all the libraries you want to depend upon. In this example we inject the routing service for navigation.
Internal TypeScript Modules
These modules are similar to namespaces. You can define an unique namespace around your code. This will encapsulate variables, interfaces and classes. TypeScript supports sub namespaces for further encapsulation.
To encapsulate our code, the module will transpile to an IIFE (Immediately-Invoked Function Expression) around our components. This will avoid global code which helps prevent variables and function declarations from living longer than expected in the global scope, which also helps avoid variable collisions.
Now that TypeScript supports object-oriented programming, we can analyse our business problem and define the business objects into entity classes. When you analyse and define these entities you can define which properties and methods each entity needs. If you have a couple entities, you can even establish a relationship. This will provide a clear view on what you want to achieve and have the possibility to create multiple instances of these classes. When building an entity class you can optionally define an interface to show what the intention of the class is.
To use your entity class in a controller you have to define the export key. This will expose the class to other classes. When exporting the interface you will use it as a data type.
Class as property
Instance of the class
As you know the controller defines the model to the view of your application, methods for every action you require and the scope where you hold a two way binding. Because TS offers an object-oriented architecture, we can use classes and interfaces instead of functions. Interfaces, like in all object-oriented languages, are a contract that must be implemented by classes that use it. When implemented, all methods and properties have to be used. Classes declare and implement the properties and methods exposed to the view. Every class has his own constructor function, in this function we can declare default property values and other initialisation code.
The interface will show you the intent of our controller and declare the properties and methods that will be used. When you look at the syntax, you see that the properties are strongly typed and the type is declared after the colon. If you aren’t certain what type a property should have, you can fall back to the general type ‘any’. When you declare methods in an interface you have to specify the necessary parameters and return types. The parameters have the same syntax as the properties.
Dependency Injection in classes
When a service is needed in your controller, it must to be injected before it can be used.
In the above example it is important that you declare the
static $injection on top of your constructor.
By doing this the constructor will recognize and initialise the injected services.
If you inject a custom service you have to reference to the related service.
TypeScript supports initialisation of your properties and injections in a constructor. When declaring properties in your class, you can declare them directly into your constructor. Although these two examples are correct you can have issues in your tests with the second example.
Be sure to notice that we are using access modifiers to tell the controller which properties we want to expose to the view. The best practice is that you put your injections and Angular services private and all your properties you want to use on your view public. When initialising strings, TypeScript makes no distinction between double or single quotes.
Controller classes use the controllerAs feature by default. So it’s important to declare this into your routes and views. In your HTML you will have to prefix your methods and properties with the ControllerAs syntax.
When you make a custom service, the code you implement is reusable and can be called in any other Angular component, including controllers and other services. It is important to know that services are singletons, so there will be only one instance for each service. With this in mind we can use the custom service to share data across all components in Angular. Communicating with an HTTP service to collect and share data with any other component by injecting the service.
For my project I used an Angular service that simplifies common verb requests with a minimum of client code. In my custom services you’ll see examples of Restangular in TypeScript. If you like to checkout what the difference is with $resource, you can check this list
In the above example, to use the Restangular service you have to install the proper typings. For services it is a best practice to declare an interface for data typing and getting a clear view of the intent. The service class will implement all methods related to the data communication with the backend and returns a promise to the controllers or services that will inject this custom service. Restangular has its own configuration you can modify in the .config component to point to the right api call. After the config you can inject the Restangular service and use its services to build up a request to the backend.
Custom directives allow you to create highly semantic and reusable components. A directive allows Angular to manipulate the DOM and add its own behaviour. These can either be a set of instructions or a JSON representation. To define a directive in TypeScript we use the directive service that Angular provides.
In the interface above we have to tell Angular what name will be used for our directive.
The attribute service will be called to add the name to its attributes.
Secondly the class has to implement the directive interface to be recognized by the compiler as a directive.
Inside the class you have to declare the prefixed properties and override the methods you will be using.
static instance() method has to be declared to let your module know that there is a new directive.
At the end you register the directive to your module with the instance as value.
Best practices can change over time. With webpack for example the registry to the module is contained in one file. TypeScript keeps on growing, and in my opinion will be the default language for many future front-end projects. When it comes to testing our code, TypeScript will provide better support because of encapsulation. Finally, this is a nice learning path to take if you want to migrate to Angular 2.