In this post we are going to talk about AngularJS, Typescript, SystemJS.
Also, we are going to work with typings definitions as well as SystemJS JSON plugin to load JSON configuration in front end code.
This is the application’s structure:
We won’t go through every little detail of this application, the link to the Github repository is here and at the end of this article if you want to jump in and take a closer look of the code.
We will go through the aspects of defining an AngularJS application with Typescript, defining our modules with SystemJS, as well as creating custom declarations to support custom structures, like the app.config.json or extend existing declarations.
Now, let’s go through the bits and bolts of the code.
As you can see, AngularJS 1.5.8 version is going to be used, with angular-route, angular-sanitize and ngstorage additional modules. The ui-bootstrap is used as well, which has dependencies on angular-animate and angular-touch modules.
SystemJS is the module loader and the systemjs-plugin-json is a SystemJS plugin to load JSON, which can be found here.
We add bootstrap as well only for aesthetic purposes.
For devDependencies, lite-server webserver is used to serve content, as well as typescript and typings node modules. Typings module is a Typescript definition manager, which can install and manage Typescript definitions, so Intellisense can be possible in Typescript code.
This file contains all the global dependencies to Typescript definitions for AngularJS, angular-ui-bootstrap, node, jQuery as well as custom definitions, which we are about to build in a few minutes.
So, we need Typescript definitions for our code, in order to have Intellisense.
To add Typescript definitions for frameworks/libraries you use the typings.json file, which is a configuration file, and then a command prompt window to install these definitions.
Below are some examples of adding definitions for angular, angular-ui-bootstrap, jquery and node.
These commands will install AngularJS, angular-ui-bootstrap, jQuery and node typings and will save them to typings.json.
We will revisit the typings definitions later, when we will add our custom ones for the app.config.json.
These commands from above will create a typings directory in application root, which will contain *.d.ts files.
--global flag is used to make the definition global to the application. It will be referenced in the index.d.ts under typings directory. This will be used from all the Typescript files automatically.
--save flag is used to save the definition to our typings.json file.
You probably have noticed that in front of the framework/library name we put something like “dt~”. This is the source option selection. The dt brings the DefinitelyTyped typings.
Typings uses the source to determine from which where it should download the Typescript definitions.
All the available typings sources (as from Typings Github page):
- npm – dependencies from NPM
- github – dependencies directly from GitHub (E.g. Duo, JSPM)
- bitbucket – dependencies directly from Bitbucket
- bower – dependencies from Bower
- common – “standard” libraries without a known “source”
- shared – shared library functionality
- lib – shared environment functionality (mirror of shared) (–global)
- env – environments (E.g. atom, electron) (–global)
- global – global (window.<var>) libraries (–global)
- dt – typings from DefinitelyTyped (usually –global)
If you want to find a definition but you are not sure about its metadata, you can search for it in the registry.
You get all the /*angular*/ definitions, with detailed information by:
Or by name:
You can find more info at the typings website.
Next, we should look at tsconfig.json. For this application, I have defined all the Typescript files to be included in the compilation process in the files array property, being explicit about them.
The files instruction, specifies the files that will be included by the compiler. Any other file not included in files array will be excluded from the compilation process. I am explicit in defining all the visible files to Typescript compiler, because I want certain of them to be compiled. And notice, I include the typings/index.d.ts as well, because I want it to be visible, essentially omitting all the other typings definitions, as they are redundant. Typescript only needs to know about the index.d.ts and it will follow the
/// <reference /> delarations when it is compiling.
The exclude array contains all the paths that should be excluded from typescript compiler.
Next, let’s visit the systemjs configuration. We want to tell SystemJS where the essential packages for the application are, so it can load these in browser.
It is required to specify an entry point, which will be the main.js script, as well as additional packages which are used throughout the application like angular, angular-sanitize, angular-route, ngstorage, ngAnimate, ngTouch, angular-ui-bootstrap and the systemjs-plugin-json plugin.
The code of systemjs-config.js is the following:
This a simple HTML file, using Twitter Bootstrap for styling and the SystemJS library with configuration and initialization code to load all of the script dependencies.
The markup should be like this:
It is pretty straight-forward what is going on here.
For SystemJS initialization with the json plugin, the json plugin is imported first, by calling
System.import("json"). This will load the plugin and will return a promise. When the plugin is successfully loaded, then the app module (the main entry point) can be loaded and essentially bootstrap the application.
This file is under the src/ directory. All the client configuration settings are defined here. Application settings are about the routes path, templateUrl, controller names, as well as modal configuration. By using this JSON file, the source code becomes cleaner, configurable, avoiding magic strings/numbers/etc.
The configuration file contains the following code:
All these are great, but we want a way to make this structure available through Intellisense for our Typescript code.
To achieve this, we need to create a *.d.ts file, declaring all this configuration.
To do so, we go to typings directory and create a custom directory there. This will hold all custom Typescript definitions.
Into custom directory, we make an appconfig directory and inside that an appconfig.d.ts file. Code of this file:
In the *.dt.s file, a module is declared, with all the configuration exported as an interface.
Now, go back to command prompt and type the following:
This will add the appconfig.d.ts in the typings.json as well as a
/// <reference /> in the index.d.ts, so it will be available globally throughout the application.
The syntax to register globally a custom definition, located in disk:
Good, app.config.json and its typings are all set.
It’s time to investigate the application module and then move to routes.ts.
In this file, an application module is instantiated, the one that is declared in the
ng-app directive of the index.html file.
Essentially, the “app” module is instantiated, registering all the additional modules, controllers and services to it. It is not necessary to export it, except you need it in other files. In this particular application it is not needed for anything else, as it served its purpose in the main.ts file.
In this file, a function is exported, which registers all the available routes for the application. Notice the use of the .json configuration file.
You should notice no any string literals exist, except the ones to find the correct route for each controller. Aside from that, everything is configurable through our app.config.json. Just to point out though, if we didn’t use the
systemjs-plugin-json the code above wouldn’t be able to compile. This is because SystemJS cannot find the json file. It is not configured to do so, so it cannot be served.
Notice, to fetch the .json file we use var and the require method. We are loading a JSON file, not a module, so import shouldn’t be used is this case. When var is used, require() is treated like a normal function.
You should be wondering, how require function is recognized by Typescript and it is not over a red squiggly line? It is because of the node typings we added first in the typings.json.
requirecomes from NodeJS, so we needed node Typescript definitions to have this without the compiler complaining.
Also, notice that parameters, as well as variables are strongly typed, like angular app module, which is described by the
$locationProvider which is described by
angular.ILocationProvider interface and lastly,
$routeProvider which is described by
These interfaces are available through DefinitelyTyped Typings. The two first,
angular.ILocationProvider come from the angular definitions, while the latter,
angular.route.IRouteProvider comes from the angular-route typings definitions. These are installed through
typings install command.
We prefer using definitely typed code., instead of
any, as it makes the code more verbose for us developers. We don’t need to know the API by heart, that’s why we are using tools like VSCode, Visual Studio, etc, or Typescript, which assist in the development process.
any is recommended only if you do not have the typings or you are quickly casting your object, in order to avoid compile-time errors.
Let’s now visit some of the controllers in application, to see how we can handle strongly typed parameters like angular
We need to identify in which definition each parameter exists. For example,
$location service are included in angular definition. So we need to install angular typings.
$uibModal are defined in angular-ui-bootstrap typings, so we need to download this from the typings registry.
Now, let’s see the home controller. HomeController’s job is to display all departments, which are fetched from a service, stored in an angular
$scope property and displayed in the template. Each department has a unique integer Id, which is used to navigate to a new route /route/:id, through a method called
As you can see in code above, I create a new class named HomeController. In its constructor, I define the service I want to inject, which is the DepartmentService imported few lines before. This service is registered in the angular module, in main.ts.
I also inject the
$location service and
$scope. The $location service can be strongly typed by the angular (alias is ng) ILocationService interface.
But we see something strange here. The $scope is of type IHomeController, which is something not included in the angular typings definition. In fact, this is something custom I created, in order to define the
departments property and the
navigateToDepartment method. If I used the ng.IScope, I may had $scope strongly typed, but Typescript compiler would give me an error, because it can’t recognize those two (departments and navigateToDepartment).
Like before, with the custom appconfig.d.ts, I create a Typescript declaration file. In typings/custom directory I create a folder named homeControllerScope, which includes an index.d.ts file. Code is the following:
I just declare an interface IHomeControllerScope which extends the ng.IScope interface. So this one contains the contract of IScope as well its custom API. And if you are wondering, what is this
Department type, it is just another custom declaration file.
I definitely recommend, when creating controller classes, to define an interface as well for the $scope object of the controller, a custom one, containing all your properties, functions, etc.
Take a look at the code in this Github repository. Clone the repository in your local machine, run the code and then have a tour. Check the typings.json configuration, as well as the controllers, how they are defined and used. Try to extend the application, by creating an edit controller for the users added in each department. Make your code strongly typed and if needed create your own declaration files.
If you liked this blog post, please like, share and subscribe! For more, follow me on Twitter @giorgosdyrra.