Monday, May 28, 2018

10 Essential TypeScript Tips and Tricks for Angular Devs

In this article, we’ll dive into a set of tips and tricks that should come in handy in every Angular project and beyond when dealing with TypeScript.

In recent years, the need for static typing in JavaScript has increased rapidly. Growing front-end projects, more complex services, and elaborate command-line utilities have boosted the need for more defensive programming in the JavaScript world. Furthermore, the burden of compiling an application before actually running it hasn’t seen as a weakness, but rather as an opportunity. While two strong parties (TypeScript and Flow) have emerged, a lot of trends actually indicate that only one may prevail — TypeScript.

Besides the marketing claims and commonly known properties, TypeScript has an amazing community with very active contributors. It also has one of the best teams in terms of language design behind it. Led by Anders Hejlsberg, the team has managed to fully transform the landscape of large-scale JavaScript projects to be nearly an exclusively TypeScript-powered business. With very successful projects such as VSTS or Visual Studio Code, Microsoft themselves is a strong believer in this technology.

But it’s not only the features of TypeScript that make the language appealing, but also the possibilities and frameworks that TypeScript is powering. Google’s decision to fully embrace TypeScript as their language of choice for Angular 2+ has proven to be a win-win. Not only did TypeScript gain more attention, but also Angular itself. Using static typing, the compiler can already give us informative warnings and useful explanations of why our code will not work.

TypeScript Tip 1: Supply Your Own Module Definitions

TypeScript is a superset of JavaScript. As such, every existing npm package can be utilized. While the TypeScript eco-system is huge, not all libraries are yet delivered with appropriate typings. Even worse, for some (smaller) packages not even separate declarations (in the form of @types/{package}) exist. At this point, we have two options:

  1. bring in legacy code using TypeScript tip 7
  2. define the API of the module ourselves.

The latter is definitely preferred. Not only do we have to look at the documentation of the module anyway, but typing it out will prevent simple mistakes during development. Furthermore, if we’re really satisfied with the typings that we just created, we can always submit them to @types for including them on npm. As such, this also rewards us with respect and gratefulness from the community. Nice!

What’s the easiest way to supply our own module definitions? Just create a module.d.ts in the source directory (or it could also be named like the package — for example, unknown-module.d.ts for an npm package unknown-module).

Let’s supply a sample definition for this module:

declare module 'unknown-module' {
  const unknownModule: any;
  export = unknownModule;
}

Obviously, this is just the first step, as we shouldn’t use any at all. (There are many reasons for this. TypeScript tip 5 shows how to avoid it.) However, it’s sufficient to teach TypeScript about the module and prevent compilation errors such as “unknown module 'unknown-module'”. The export notation here is meant for the classic module.exports = ... kind of packages.

Here’s the potential consumption in TypeScript of such a module:

import * as unknownModule from 'unknown-module';

As already mentioned, the whole module definition is now placed in the type declaration of the exported constant. If the exported content is a function, the declaration could look like this:

declare module 'unknown-module' {
  interface UnknownModuleFunction {
    (): void;
  }
  const unknownModule: UnknownModuleFunction;
  export = unknownModule;
}

Of course, it’s also possible to use packages that export functionality using the ES6 module syntax:

declare module 'unknown-module' {
  interface UnknownModuleFunction {
    (): void;
  }
  const unknownModule: UnknownModuleFunction;
  export const constantA: number;
  export const constantB: string;
  export default unknownModule;
}

TypeScript Tip 2: Enum vs Const Enum

TypeScript introduced the concept of enumerations to JavaScript, which did represent a collection of constants. The difference between

const Foo = {
  A: 1,
  B: 2,
};

and

enum Foo {
  A = 1,
  B = 2,
}

is not only of syntactical nature in TypeScript. While both will be compiled to an object (i.e., the first one will just stay as is, while the latter will be transformed by TypeScript), the TypeScript enum is protected and contains only constant members. As such, it would not be possible to define its values during runtime. Also, changes of these values will not be permitted by the TypeScript compiler.

This is also reflected in the signature. The latter has a constant signature, which is similar to

interface EnumFoo {
  A: 1;
  B: 2;
}

while the object is generalized:

interface ConstFoo {
  A: number;
  B: number;
}

Thus we wouldn’t see the values of these “constants” in our IDE. What does const enum now give us? First, let’s look at the syntax:

const enum Foo {
  A = 1,
  B = 2,
}

This is actually the same — but note, there’s a const in front. This little keyword makes a giant difference. Why? Because under these circumstances, TypeScript won’t compile anything. So we have the following cascade:

  • objects are untouched, but generate an implicit generalized shape declaration (interface)
  • enum will generate some boilerplate object-initializer along with a specialized shape declaration
  • const enum doesn’t generate anything beside a specialized shape declaration.

Now how is the latter then used in the code? By simple replacements. Consider this code:

enum Foo {
  A = 1,
  B = 2
}

const enum Bar {
  A = 1,
  B = 2
}

console.log(Bar.A, Foo.B);

Here we end up in JavaScript with the following outcome:

var Foo;
(function (Foo) {
  Foo[Foo["A"] = 1] = "A";
  Foo[Foo["B"] = 2] = "B";
})(Foo || (Foo = {}));
console.log(1 /* A */, Foo.B);

Note that 5 lines alone have been generated for enum Foo, while enum Bar only resulted in a simple replacement (constant injection). Thus const enum is a compile-time only feature, while the original enum is a runtime + compile-time feature. Most projects will be well suited for const enum, but there may be cases where enum is preferred.

Continue reading %10 Essential TypeScript Tips and Tricks for Angular Devs%


by Florian Rappl via SitePoint

No comments:

Post a Comment