Categories
TypeScript

JavaScript Object Features in TypeScript — Maps and Sets

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at how to use maps to store key-value pairs, and sets to store items without duplicates.

Storing Key-Value Pairs with Maps

We can store key-value pairs with maps.

The difference between maps and object literals is that we can have keys other than strings and symbols.

We can define a map with JavaScript’s built-in Map constructor.

For instance, we can write the following to do that:

const data = new Map();
const key = {
  foo: 'bar'
};
data.set("foo", "bar");
data.set(key, 2);

As we can see, we have a map with a string key and an object key in a map.

The set method stores a value with the given key.

To get a value by a key, we can use the get method with the key as an argument.

There’s also the keys method to get an iterator with the keys of a map.

The values method returns the values of a map.

The entries method returns an iterator with the key-value pairs of a map.

This is the default iterator for maps.

Use Symbols for Map Keys

Symbols can be used for Map and object keys.

For instance, we can write:

const data = new Map();
const symbol = Symbol();

data.set("foo", "bar");
data.set(symbol, 2);

Each symbol that’s created is different so we’ve to store it in a variable so that we can get the value with the symbol key later.

This is also the same for non-primitive keys.

Therefore, we can have multiple symbols with the same name but with different references.

For instance, we can write:

const symbol1 = Symbol('foo');
const symbol2 = Symbol('foo');

They have the same name but are different.

So:

symbol1 === symbol2

would return false .

We can get the value with the given symbol key by writing:

const value = data.get(symbol);

Storing Data by Index

We can store data without duplicates with a Set instance.

For instance, we can write:

const set = new Set([1, 2, 3]);

Then we get a set with those values returned.

Sets can’t have duplicates, so we if we write:

const set = new Set([1, 2, 3, 1]);

We still get a set with 1, 2, and 3 in it as only the first instance of something is kept.

Sets have the size property that returns the size of a set.

Set instances have some useful methods to get items from them, add/remove items, and iterate through them.

add takes the value that we want to add to a set as the argument.

entries returns an iterator for all entries in the order that they were added.

has returns true is a set has the specified value.

forEach takes a callback that’s run on every entry of a set.

Conclusion

We can use maps to store key-value pairs. It can store keys that aren’t strings or symbols, unlike objects.

Sets can be used to store data without duplicates.

They’re both iterable objects, and we can call a method to return iterators from them.

Categories
TypeScript

TypeScript Project Setup- Hot Reloading, Libraries and Build Targets

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at how to create a TypeScript project that does hot reloading.

We also look at how to choose type definitions to include in our project so that we can use the features we want.

Using Watch Mode

To make our lives easier, we should make sure that our project recompiles and reloads each time we save our code.

This way, we don’t have to recompile and restart our app every time we change code.

With watch mode, we watch the source code files that are changed, and then the compiler will rebuild the app automatically.

Our TypeScript project should already have the TypeScript compiler installed already.

Then we run:

tsc --watch

to watch for code changes and recompile the code.

Now if we change our code, we should see messages when building and show any compiler errors if they’re found.

Automatically Executing Code After Compilation

tsc --watch only recompiles the files and don’t run them.

To run them, we’ve to use another program.

The ts-watch package starts the compiler in watch mode, observes the 9output and runs commands based on compilation results.

For instance, we can write:

npx tsc-watch --onsuccess "node dist/index.js"

to run tsc-watch without installing it and runs dist/index.js with Node on success.

Starting the Compiler Using NPM

We can add a start script in package.json to run commands to start the project.

So we can write:

{
  //...
  "scripts": {
    "start": "tsc-watch --onsuccess "node dist/index.js""
  },
  //...
}

Version Targeting

With the TypeScript compiler, we can target different versions of JavaScript in our final build.

We can go as low as ES3 to the newest versions.

This way, we can support legacy browsers easily.

For instance, we can write:

{
  "compilerOptions": {
    "target": "es5",
    //...
  }
}

to support browsers like Internet Explorer, which don’t have the support of the latest JavaScript features built-in.

The following versions are supported.

es3–3rd edition of the language. It’s the default value when target isn’t specified. It’s defined in 1999

es5 — the 5th edition of JavaScript released in December 2009

es6–6th edition of JavaScript which added lots of features for creating complex apps like classes, modules, arrow functions, promises, etc.

es2015 — same as es6

es2016–7th edition of the JavaScript specification includes the includes method for arrays and the exponentiation operator

es2017–8th edition of the JavaScript specification. It adds features for inspecting objects and async/await

es2018–9th edition of the JavaScript specification. It adds the spread and rests operations or objects ad string handling and async operations.

esNext — target features that will be included with the next edition of the JavaScript specification.

Since ES6, JavaScript versions are indicated by the year.

ES6 is the same as ES2015. Then JavaScript moved to release new features incrementally each year.

Therefore, the version changed to the year.

When file changes are saved, then the code will be compiled and run.

The generated code is in the dist folder.

Setting the Library Files for Compilation

To enable features that aren’t available in the target version, we can change the target.

We can set the lib compiler options with the following options.

es5 , es2015 , es2016 , es2017 and es2018 change the type definition file that correspond to the JavaScript version.

esnext lets us include proposed additional to JavaScript into our code.

dom include DOM libraries.

dom.iterable provide type information for the additions to the DOM API and allow iteration over HTML elements.

webworker inclusion of web worker features.

es2015.core include type information for the main features introduced by ES2015.

es2015.collection settings include type information for Map and Set constructors.

es2015.generator and es2015.iterable includes type information for generators and iterable features.

es2015.promise — includes type information for promises.

es2015.reflect — includes type information for reflecting features to get access to properties and prototypes

es2015.symbol , es2015.symbol.wellknown— includes type information about symbols

We include lib settings under the compilerSections section.

For instance, we can write:

{
  "compilerOptions": {
    "target": "es5",
    "outDir": "./dist",
    "rootDir": "./src",
    "noEmitOnError": true,
    "lib": ["es2015", "dom", "es2015.collection"]
   }
}

Conclusion

To use the JavaScript features we want, we’ve to include the TypeScript type definitions to include in our protect.

We can also target the version of JavaScript we want for our TypeScript project.

Categories
TypeScript

JavaScript Object Features in TypeScript — Static Methods, Generators, and Collections

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at how to define static methods and defining, using iterators and generators, and accessing collections.

Defining Static Methods

Static methods are methods that are available for all instances of the class.

They’re accessed through the class rather than the object it creates.

For instance, we can create a static method by writing:

class Animal {
  constructor(name) {
    this.name = name;
  }

  static getType() {
    return 'animal';
  }
}

Then we can write the following code to call getType:

const type = Animal.getType();

We called getType on Animal rather than an instance of it.

Iterators and Generators

Iterators are objects that return a sequence of values.

They’re used with collections and they can be used without them.

An iterator defines a function named next that returns an object with value and done properties.

The value property returns the next value in the sequence and the done property is set to true when the sequence finishes.

Generator functions are functions that return iterators.

We use the yield keyword to return the next item in the sequence.

For instance, we can write:

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

The function* keyword returns a generator that we can use to return 1, 2, and 3 in sequence.

Then we can write the code to return a generator, that we can use to return the next value:

const generator = gen();
let done = false;
while (!done) {
  const next = generator.next();
  console.log(next.value);
  done = next.done;
}

Calling gen returns a generator.

Then we use it to get the items with the next method until done is true .

The JavaScript runtime creates the next function and runs the generator function until it reaches the yield keyword.

yield provides the next value in the sequence.

We can use generators with the spread operator.

For instance, we can write:

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

const generator = gen();
const arr = [...generator];

This will spread all the values that are after yield into an array, so we get:

[1, 2, 3]

as the value of arr .

Defining Iterable Objects

We can define our own iterable objects in addition to generators.

For instance, we can have a generator method in our object.

We can write:

const obj = {
  * gen() {
    yield 1;
    yield 2;
    yield 3;
  }
}

const generator = obj.gen();

We just put the gen generator as a method of obj and the call it.

However, it’s a bit awkward to use.

We can make this cleaner by using the Symbol.iterator symbol as the name of the method instead:

const obj = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  }
}

This will make obj an iterable object, then we can write:

const arr = [...obj];

to spread the returned values.

And we get [1, 2, 3] as the value of arr .

JavaScript Collections

JavaScript objects are very flexible. We can get the keys and values of an object to do what we like with it.

With Object.keys , we can get the keys of an object as an array.

And with Object.values , we can get the values of an object as an array.

We can use Object.keys as follows:

const obj = {
  a: 1,
  b: 2,
  c: 3
};

for (const key of Object.keys(obj)) {
  console.log(key);
}

Then we get a , b and c logged.

Object.keys only return the own string keys of an object.

Likewise, we can return an array of values with Object.values .

We can write:

const obj = {
  a: 1,
  b: 2,
  c: 3
};

for (const key of Object.values(obj)) {
  console.log(key);
}

Then we get the numbers 1, 2, and 3 logged in the console.

It also returns the own values of an object for properties with string keys.

Conclusion

We can use generator functions to return a generator to return something in sequence.

To access the keys and values of objects, we can use Object.keys and Object.values respectively.

Static methods can be defined on a class and be available by accessing them directly from the class.

Categories
TypeScript

How Does TypeScript Work?

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at how TypeScript makes JavaScript projects better.

What is TypeScript?

TypeScript is a superset of the JavaScript language that lets us produce safe and predictable code.

The code that it produces can be run in any JavaScript runtime.

It provides us with type checks which JavaScript lacks.

Should We Use TypeScript?

TypeScript isn’t the solution to all the problems in JavaScript projects.

We should know what TypeScript is good for.

TypeScript is focused on developer productivity through static typing.

It makes JavaScript type system easier to work with.

It provides us access control keywords and enhances the class constructor syntax.

We can write TypeScript code with JavaScript or TypeScript-exclusive syntax.

All the code goes through the TypeScript compiler, which is built to JavaScript.

The combination of JavaScript and TypeScript provides us with a flexible combination of both.

We can apply TypeScript features selectively.

Limitations of the Productivity Features

Writing TypeScript is mostly writing JavaScript with some extra features.

TypeScript enhances the type system of JavaScript and provides flexible types just like JavaScript.

If we understand JavaScript’s type system, then we understand why TypeScript’s type system is the way it is.

TypeScript provides us not only with a static type system, but also more dynamic types like union types, intersection types, literal types, and more.

JavaScript Version Features

Older JavaScript runtimes don’t support many modern features that are universally supported.

Therefore, we need a compiler like a TypeScript compiler to transform code to the JavaScript runtimes that we’re targeting.

The compiler does a good job of most transforming to JavaScript code that’s more compatible.

Data Types of JavaScript

To understand TypeScript, we’ve to understand the data types of JavaScript.

We won’t be very productive with TypeScript id we don’t understand the type system of JavaScript.

Confusion by JavaScript

The foundation of data storage in JavaScript is variables.

We can declare variables with let or const .

let variables can be reassigned while const ones can’t.

JavaScript Types

JavaScript has a clear type of system. And the rules for it are applied consistently throughout the language.

The most basic JavaScript data types are primitive values and the object compound type.

The primitives are number, string, boolean, symbol, null, undefined.

object is a compound type.

number is a data type used for representing all numeric values. JavaScript doesn’t distinguish between integer and floating-point values.

string is a type used for representing text data.

boolean can either be true or false

symbol represents unique constant values, like keys in an object.

null represents a nonexistent reference.

undefined is used by a variable that’s defined but hasn’t been assigned a value.

object is the type used to represent compound values, formed bu individual properties and values.

Working with Primitive Data Types

In JavaScript, we don’t write out the data type explicitly.

It figures out the type of its variable on its own when we assign it a value.

For instance, we write:

let foo = "bar";

to declare a string variable.

If we need to check the type, we write:

typeof foo

to return the type of the variable.

typeof is an operator that identifies the value type and returns 'string' .

typeof null returns 'object' , which isn’t the correct behavior, but it’s already implemented in all runtimes and code, so we can’t use typeof to check for null .

Type Coercion

JavaScript does data type coercion of its variables when it does certain operations.

It produces consistent results, we just have to know how it works.

If we use the == operator to compare objects, then type coercion will be done before comparison operations are done.

For instance, we may have:

let applePrice = 1;  
let orangePrice = '1';  
if (applePrice == orangePrice) {  
  //...  
}

Then both will be converted to numbers before comparison.

When we write:

let totalPrice = applePrice + orangePrice;

then both will be converted to strings before concatenation, which is probably not what we want.

Avoiding Unintentional Type Coercion

To make our lives easier, we should take steps to avoid unintentional data type coercion.

To do that, we can use the === operator for comparisons and convert types explicitly first before doing concatenation.

Conclusion

TypeScript works by adding enhanced syntax to JavaScript and then transforming it to JavaScript after the TypeScript compiler does its own checks.

It doesn’t change JavaScript’s type system. Instead, it adds more checks to it.

Categories
TypeScript

Setting Up Our TypeScript Project

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at how to create a project with the TypeScript compiler and write some code.

Installing Packages

We can install packages by using some npm commands.

npm install performs local install of the packages specified in the package.json file.

npm install package@version performs a local install of a specific version of a package and updates the package.json to add the package in the dependencies section.

npm install --save-dev package@version performs a local install of a specific version of a package and updates the package.json file to add the package to the devDepdencies section.

devDepencies section adds the dependencies required for the development of the project but it’s not part of the app.

npm install --global package@version performs a global install of a specific version of a package.

npm list will list all the local packages and their dependencies

npm run runs one or more scripts defined in the package.

npx package runs the code contained in a package.

We should exclude the node_modules folder because it has a large number of files and may contain platform-specific components that don’t work when the project is checked out.

Instead, we should check out the project and let npm install create the node_modules folder.

To maintain consistency in the packages each time it’s checked out, a package.json file is created.

With it, version changes to packages we use for dependencies won’t be affected.

TypeScript Compiler Configuration File

The TypeScript compiler, tsc is responsible for compiling TypeScript files.

It’s the compiler that’s responsible for implementing TypeScript features like static types.

The result of that the compiler outputs would be pure JavaScript with all the TypeScript keywords and expressions removed.

A sample tsconfig.json may have the following:

{  
  "compilerOptions": {  
    "target": "es2018",  
    "outDir": "./dist",  
    "rootDir": "./src"  
  }  
}

They have the following meaning.

compilerOptions is the section that groups the settings that the compiler will use.

files specifies the files that will be compiled and overrides the behavior where the compiler searches for files to compile.

include is a setting used to select files for compilation by pattern. By default, .ts , .tsx , and .d.ts extensions will be selected.

exclude is a setting is used to exclude files from compilation by pattern.

compileOnSave , when it’s set to true , this setting is a hint to the code editor that it should run the compiler each time a file is saved.

This option isn’t supported bu all editors,

We can set these options is our project doesn’t have a typical TypeScript project structure.

The TypeScript package has type declaration for different versions of Javascript and for the APIs that are available in Node.js and browsers.

We can use tsc --listFiles and list the files that will be included in the build process.

Also, we can use npx to run tsc --listFiles without install tsc globally.

We can use npx tsc --listFiles to do that.

Compiling TypeScript Code

To compile the TypeScript code into a JavaScript bundle, we can use the tsc command to build our project.

This is from the TypeScript compiler that we installed in our package with:

npm install --save-dev typescript

Once we run tsc , we should have a dist folder by default with the built files.

The extension would be .js for JavaScript.

The file won’t have any TypeScript-specific expressions in them.

We shouldn’t edit this folder since it’ll be overwritten next time the compiler runs.

For the same reason, we shouldn’t check in the dist folder.

Compiler Errors

If we write our TypeScript code incorrectly, we may get compiler errors from the TypeScript compiler.

For instance, if we have the following function:

const printMsg = (msg: string): void => {  
  console.log(msg);  
};

Then we would pass a string into it.

However, if we pass in a number to it as follows:

printMsg(5);

Then we would get a type mismatch error.

We would get:

Argument of type '100' is not assignable to parameter of type 'string'.ts(2345)

since we passed in a number.

This error is a benefit that TypeScript brings.

We get an error if the type of the argument doesn’t match what’s specified.

An editor that supports TypeScript would show us this error.

If we run tsc , we’ll see the same error as well if we haven’t corrected it.

However, we can disable emitting compiler errors with the noEmitOnError option set to true .

For instance, we can write:

{  
  "compilerOptions": {  
    "target": "es2018",  
    "outDir": "./dist",  
    "rootDir": "./src",  
    "noEmitOnError": true  
  }  
}

Conclusion

We should see errors in our code if our code has type mismatches.

Also, we’ve to install packages with npm in except for the most trivial projects.