TypeScript, Type Definitions, and modular JavaScript in an Angular World

In the plainest terms, TypeScript ( referred to as TS in the duration of this article ) is a way to provide strong typing to your JavaScript code. In other words, it enables you to create code that behaves predicably and consistently by making sure X variable is always the type of data you meant it to be. That way you dont have some variable that's an integer that accidentally saves some number input as a string and then screws up your application. Let's look at a quick example for clarification.

let myBalance: Int = 300.00;

That's the simplest example. Now anytime I use myBalance anywhere, my code insures that the value im using will be an integer. If I try to set it later to a string like myBalance = '300.00' then TypeScript will throw an error for me.

TODO: Add an example of an interface and explain that concept

That's the basis for what TypeScript is.

But Wait... There's more: What does TS give me?

If one were to stop there, you could say that's a nice addition but not a major upgrade to the language. JavaScript has been a language that has been evolving rapidly over the past few years. Between all of the frameworks like Angular, React, Vue and the constant iterations of JS Specs, the language continues to push new boundaries. With that being said, every browser has a different set of the newest JS features that it has adopted. One browser may support lexical scoping while another may not, while another may support const and let keywords while a different one doesnt.

Typically, in order to use the latest JS features you have to use compiler libraries like Babel or Traceur. TS allows you to use next gen JavaScript right now. In addition to typings that TS provides, it gives you access to almost if not all of the ES2015 spec so you can do things like classes and interfaces right out of the box. It does this by using the TSC compiler tool that comes with TS and it compiles your advanced JS down to basic JS that all browsers understand. We'll look more at this later.

Beyond language interpolation and compilation TS also gives many modern IDE's code hints as you work! VSCode calls these Intellisense. As you hover over a variable, class, or other element Vscode will reccomend what you need to implement said item. It will highlight when you dont use the right number of arguments in a function call, the wrong variable type somewhere, or when you forget to import a file. It's magic. How is this black magic done you ask? TS uses the interfaces and type hints you define as you write your code. What if I use a third party library you may ask? Not to worry, TS can actually load what are called type definition files usually named like myFile.d.ts and that actually defines types for how the library should behave. Pretty amazing. Many libraries like jQuery or Jasmine even have type definitions already written. These can be installed or managed via NPM. TS has even gotten so popular that there is a repository just for searching types. It's like NPM for TypeScript and it's called DefinatelyTyped.

The thing I like most about TypeScript is how it allows you to organize your code. ES2015 allows you to organize your code into packages of related functionality called modules. This concept has been around for ever but has become a trend and a reality in the JS world only in the past few years. In the past, people used to use things like requirejs or browserify to create AMD or CommonJS modules and then import them. CommonJS being the type that node supports. People have gone back and forth over where to use each of these and why each is better. The constant back forth,, pro's and con's of each, and the sheer popularity of both led to the creation of the UMD or Universal Module Definition standard. It is basically a wrapper that exports both types depending on whichever is defined. jQuery is a library which implements such things.

Regardless, in order to use modules and classes in JavaScript in the browser, you have to use a bundler or tool of some kind. TypeScript also handles this for us. Not only does it handle this for you, but it allows you to create knicknames or aliases if you were to modules. So instead of importing myLib from a super long name, you can use a shortcut to give it a nice name. Like so:

import { myLib } from 'vendors/somelib/somefolder/dist/file';

becomes:

import { myLib } from 'libs/file';

You can check out more on this importing topic by looking at the NgModules post here.

TODO: link this up

This among other settings is done through TS's configuration which you set in a file called .tsconfig.

The .tsconfig.json file

.tsconfig is where the magic happens. Let's take a look at an example from angular and then we'll go over some of the properties.

"compilerOptions": {
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "module": "commonjs",
    "target": "es5",
    "types": [
      "jasmine",
      "jquery",
      "semantic-ui",
      "mongoose",
		"dotenv",
		"graphql"
    ],
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2017",
		"dom",
		"esnext"
    ],
    "baseUrl": ".",
    "paths": {
      "@myworkspace/*": [
        "libs/*"
      ]
    }
  },
  "exclude": [
    "node_modules",
    "tmp"
  ]
}

Types

The types will include all type defs in the node_modules/@types directory by default. Antying in definately typed can be added there via npm. If you specify the types property though, only types listed there will be included. Furthermore, setting the typeroot property means that TS will only look at types in that specified directory, not the standard @types directory. This is handy if you want to change the directory where type definitions are stored or something.

If you have types defined in your compilerOptions, only these types will be checked against by TSC.

Libs

TS checks against a standard set of rules. This set is determined by the target. Which version of JS you're going for (ES5, ES6, ect..). This list of standard ruls is called the libs. The standard libs checked against will be different depending on which target you select.

In additon to the default libs used, you may add your own libs or overwrite the standard ones that will be used. The lib property is a set of definitions for to check against.

By setting the lib property, you are telling TS to only check against libs in that set.

BaseUrl and Paths: Aliases that make handsome imports

As mentioned above, rather than having a nasty import
you can set aliases and use those for convinience.

By default, TS uses relative paths for module resolution. When you set the baseUrl property you tell it to circumvent that and look for a corresponding property in the paths property.

Advanced TS: Extending your TS configs

TODO: Finish this using NX as the example

Common TS Errors and how to solve them


TS2304: No Typings for X Library

Common situation with this is jQuery and a plugin library for it like SemanticUI.

Could not find a declaration file for module 'moduleName'. '/path/to/moduleName/lib/api.js' implicitly has an 'any' type.

OR

TS2304 - Cannot find name '$'

OR

TS2307 Cannot find module 'Z'
Why do you get the error?

So why do you get this error? The reason is that Typescript can't find any type definitions associated with that jQuery by default.

Let's take that a step further though and explore a little deeper. When we import jQuery, it doesn't come with any typings since it's not written in TS. So essentially, it's not really an error. This is just TS's way of saying "Hey, I don't know what this is!". So that's nice and all but how do we correct this so we can get our code to compile and stop yelling at us?

First Glance solution: Declare the type yourself.

So there are a few ways to crack this nut. The first one is this, if you only need the library in question in a few places, you can just declare the data type at the top of your class or file you're working with like so:

//... more code above

declare var $:any;

ngAfterViewInit(){
   $('.someCoolElement').goesBam();
}

This let's TS know not worry about $ he's legit. This solution will get you by but it's not ideal. I say that because if you have code in multiple places that relies on this typing, then you will have to write that over and over again all over your app. Ain't nobody got time fo that! Also, if you have a third party jQuery plugin you're using like bootstrap or semanticUI then this will essentially break it's type definitions since you just redeclared the type for jQuery.

Better approach: install some typings

So a more ideal way to go about solving this is just to teach TS what $ is. Teach it to recognize jQuery and what API to expect from it. The way you do this is to install the typings. You can see more about this above where we described the tsconfig compiler options and types. Once you install the types, just restart your the ng cli or comipler. Boom, now you can eliminate all of those nasty inline declare statements in your .ts files and everybody plays nice together.

There used to be typings managers like one creatively called typings or another popular manager called TSD. These still can be used but TS's official way of handling typings now is via NPM.

2) TS2687: Conflicting Type Definitions: All declarations of 'observable' must have identical modifiers

This happens when a library has it's 1 definition type but another library exports it's own type. I recently ran into this one when RXJS updated it's definition of Observable but zen-observable wasn't up to date with the latest definition.

How do you fix this? You google and play with your package.json version numbers until you are driven to drink :)
In all seriousness, often you must downgrade one package until the maintainer of package a updates its type definitions to be compatible with the type defs of package b.