Categories
TypeScript

How to Create Projects with the TypeScript Compiler

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.

We also look at how to install packages, what the version number means.

Also, we look at the project structure of a typical TypeScript project.

Getting Started

We can create a folder by creating a project folder.

Then inside it, we run:

npm init --yes

to create a package.json file.

Then we install Node packages by running:

npm install --save-dev typescript
npm install --save-dev tsc-watch

to install the TypeScript compiler and a program to reload our program as it changes respectively.

Everything will be installed into the node_modules folder.

Then we add a tsconfig.json to add our compiler options and add:

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

Then our built code will be in the dist folder and our code should be in the src folder.

The target is the JavaScript version that our build artifacts will target.

Writing Code

We can then create an index.ts file in the src folder and start writing some code.

For instance, we can write:

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

print("hello");

We created a print function that takes a msg parameter.

The word string after the colon is the parameter type.

void is the return type of the function. It means that our function returns nothing.

Project Structure

A TypeScript project folder consists of the following items:

  • dist — the folder that has the output from the compiler
  • node_modules — the folder containing the packages that the app and dev tools require
  • src — the folder containing the source code files that will be compiled by the TypeScript compiler
  • package.json — the folder containing the top-level package dependencies for a project
  • package.json — a file containing the complete list of package dependencies of a project
  • tsconfig.json — a file having the config settings of the TypeScript compiler

Node Package Manager

Most TypeScript needs dependencies from the outside.

NPM has the most TypeScrtipt packages for JavaScript and TypeScript project.

Since TypeScript is a superset of JavaScript, we can use any JavaScript package in TypeScript code.

NPM follows the chain of dependencies to work out which version of each package is required and download everything automatically.

Anything that’s saved with the --save-dev or -D option is saved in the devDependencies section of package.json .

Global and Local Packages

Package managers can install packages so they’re specific to a single project.

This is the default option.

Some packages that are needed to run on the computer can be installed as global packages.

For instance, the TypeScript compiler would be a global package since we would need it on the whole project.

Node packages mostly use semantic versioning.

There are also a few symbols to denote the package version in different ways.

For instance, we can denote the version number exactly with 1.0.0 .

The * accepts any version of the package to be installed.

>1.0.0 or >=1.0.0 means that we accept any version of a package that’s greater than or greater than or equal to a given version.

<1.0.0 or <=1.0.0 means that we accept a version that’s less than or less or equal to the given version.

~1.0.0 means that we accept a version to be install even if the patch level number doesn’t match.

So 1.0.1 is considered equivalent to 1.0.0 is a ~ is prefixed to a version number.

^1.0.0 will accept any version even if a minor release number or the patch number doesn’t match.

So 1.0.0 and 1.1.1 are considered equivalent if a ^ is prefixed to a version number.

Conclusion

Node packages go back semantic versioning. We can specify whether we want an exact match or not in package.json .

We can add the TypeScript compiler to get started with a TypeScript project.

Once we have that, we can add TypeScript-specific code to our projects.

Also, we can specify the build target version of our TypeScript project.

Categories
TypeScript

JavaScript Object Features in TypeScript — Methods and this

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 some features of JavaScript that should be used in TypeScript projects, including methods and this.

Defining Methods

Methods are properties of objects that are function valued.

They’re also part of a class, where we can call them after we instantiate the class.

For instance, we can define methods in objects by writing:

let obj = {
  greet() {
    console.log("hello");
  }
};

In the code above, we defined an obj object that has the foo method.

We can then call it by running:

obj.greet();

Now we get 'hello' displayed on the console log.

The this Keyword

this is a confusing keyword for many JavaScript developers.

This is because this can refer to anything depending on its location.

If it’s in an object, then this refers to the object.

If it’s in a class, the this refers to the class that it’s in.

If it’s in a traditional function, then this refers to the function.

For instance, if we have:

let obj = {
  name: "joe",
  greet() {
    console.log(`hello ${this.name}`);
  }
};

obj.greet();

Since this refers to obj in the greet method.

Then this.name refers to 'joe' .

Therefore, we see ‘hello joe’ in the console log.

If it’s in class, then this would be the class. For instance, if we have:

class Foo {
  constructor() {
    this.name = "joe";
  }

  greet() {
    console.log(`hello ${this.name}`);
  }
}

new Foo().greet();

Then we have this.name set to 'joe' again since we did that in the constructor.

So, we get the same result when we run:

new Foo().greet();

this Keyword in Stand-Alone Functions

We can use the this keyword in a traditional function.

For instance, if we write:

function greet(msg) {
  console.log(`${this.greeting}, ${msg}`);
}

Then we can set the value of this by using the call method and call the function:

greet.call({ greeting: "hello" }, "joe");

We change the value of this to { greeting: “hello” } by passing it into the first argument of the call method.

A value assigned names without using the let , const or var keyword is assigned to the global object.

For instance, we can write:

function greet(msg) {
  console.log(`${this.greeting}, ${msg}`);
}

greeting = "hello";
greet("joe");

Then we see that this.greeting is 'hello' since greeting is a property of the global object.

this is the global object as we can see.

Changing the Behavior of this

The call method can change the behavior of this .

Also, we can use the bind method to return a function with a different value of this .

For instance, we can write:

function greet(msg) {
  console.log(`${this.greeting}, ${msg}`);
}

const helloGreet = greet.bind({ greeting: "hello" });
helloGreet("joe");

By calling the bind function, we change this to an object, we passed in as an argument.

Then we can call the returned function and see 'hello, joe’ in the console output.

bind and call are both available to traditional functions only.

Arrow Functions

Arrow functions don’t work the same way as regular functions.

They don’t have their own this value and inherit the closet value of this tey can find when they’re run.

For instance, if we have:

let obj = {
  greeting: "Hi",
  getGreeter() {
    return msg => console.log(`${this.greeting}, ${msg}`);
  }
};

const greeting = obj.getGreeter()("joe");

Then when we call getGreeter , we get the arrow function returned.

this would be the object since the value of this inside getGreeter would be obj .

The second parentheses call the returned function with 'joe' .

Arrow functions don’t have their own value of this , so it takes the value of this from getGreeter .

Conclusion

We can add methods to objects and classes and use them in our TypeScript code.

The value of this varies depending on location. To make sure our code refers to the right value of this , we’ve to know what this takes on at the location that it’s in.

The value of this can be overridden with bind and call , which are available to traditional functions.

Categories
TypeScript

JavaScript Object Features that we can use in TypeScript Code

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 more features that can be used within our TypeScript code.

Working with Objects

JavaScript objects are collections of properties, each of which has a name and value.

For instance, we can write:

let person = {
  firstName: "jane",
  lastName: "smith"
};

We have a person object that have firstName and lastName properties.

Then we can access the properties by using the dot notation as follows:

const firstName = person.firstName;

Adding, Changing, and Deleting Object Properties

We can add properties to an object dynamically.

They can also be updated and removed on the fly.

To add a property to an object, we can write:

person.age = 20;

Given that we have the persons object, we can add a property to by using the dot notation and assigning a value to it.

Likewise, we can change it by using the same dot notation as we did above.

Deleting object properties can be done with the delete operator.

For instance, we can use the delete operator as follows:

delete person.age;

Then the age property will be removed from the person object.

Guarding Against Undefined Objects and Properties

We got to check our code against undefined object properties.

To do that, we can use the || operator.

For instance, we can write:

let price = apple.price || 0;

to see if the apple.price is undefined . If it is, then we set 0 to price.

Otherwise, we set apple.price to price .

This also works with null .

Using the Spread and Rest Operators on Objects

The spread operator can also be used with objects.

For instance, we can write:

let person = {
  firstName: "jane",
  lastName: "smith"
};

Then we can make a shallow clone of an object by using the spread operator.

For instance, we can write:

let clone = { ...person };

All the own properties will be copied over.

We can also add additional properties to the cloned object by writing:

let clone = { ...person, gender: 'female' };

We can also replace an existing property with new ones in the cloned object.

If we have the person object, we can create a clone of it and replace a property of the clone by writing:

let person = {
  firstName: "jane",
  lastName: "smith"
};

let clone = { ...person, firstName: "may" };

Now we replaced 'jane' with 'may' in the clone object while keeping the person object as-is.

We can also use the spread operator on the left side to assign properties to variables.

The spread operator will have an object that doesn’t have variables assigned to them in an object.

For instance, we can write:

let { firstName, ...props } = person;

This will assign the value of person.firstName to firstName and the remaining properties will be assigned to props .

Therefore, props will have { lastName: “smith” } as its value.

Getters and Setters

Objects can have getters and setters.

To define them, we use the get and set keywords to define getters and setters respectively.

For instance, we can write:

let person = {
  firstName: "jane",
  lastName: "smith",
  get name() {
    return `${this.firstName} ${this.lastName}`;
  },
};

to define a getter called name . Then we can access the getter’s return value by writing:

const name = person.name;

To define a setter, we can use the set keyword to let us set the value the way we want.

For instance, we can write:

let person = {
  firstName: "jane",
  lastName: "smith",
  _age: 20,
  get name() {
    return `${this.firstName} ${this.lastName}`;
  },
  set age(age) {
    this._age = age;
  }
};

We can then set age by running:

person.age = 26

Which will make person._age have the value 26.

Conclusion

JavaScript objects are just a collections of key-value pairs.

We can use getters and setters to let us get and set the values of an object.

Also, we can clone an object with the spread operator and assign values to variables.

Finally, we can add, update, and delete properties from objects dynamically.

Categories
TypeScript

Ways to Write Better JavaScript — Use TypeScript

The way we write JavaScript can always be improved. As the language evolves and more convenient features are added, we can also improve by using new features that are useful.

In this article, we’ll look at some ways to write better JavaScript by using TypeScript.

Use TypeScript

TypeScript is a natural extension to JavaScript. It lets us write JavaScript code that’s type-safe. Therefore, we can use it to prevent lots of data type errors that would otherwise occur if we didn’t use TypeScript.

Also, it provides autocomplete for things that otherwise wouldn’t have the autocomplete feature like many libraries. They use TypeScript type definitions to provide autocomplete for text editors and IDEs to make our lives easier.

TypeScript doesn’t turn JavaScript into a different language. All it does is add type checking to JavaScript by various type-checking features.

Therefore, all the knowledge that is used for JavaScript all apply to TypeScript.

For instance, we can create a function with TypeScript type annotations as follows:

const foo = (num: number): number => {
  return num + 1;
}

In the code above, we have the foo function with a num parameter that’s set to the type number . We also set the return type to number by specifying the type after the : .

Then if we call the function with a number, the TypeScript compiler will accept the code.

Otherwise, it’ll reject the code and won’t build the code. This is good because JavaScript doesn’t stop this from happening.

Interfaces

TypeScript provides us interfaces so that we know the structure of an object without logging the object or checking the value otherwise.

For instance, we can create one as follows:

interface Person {
    name: string;
    age: number;
}

Then we can use it as follows:

const person: Person = { name: 'jane', age: 10 }

If we miss any of these properties, then we’ll get an error as the TypeScript compiler is looking for them.

We can also use it to enforce a class implementation as follows:

interface PersonInterface {
    name: string;
    age: number;
}

class Person implements PersonInterface {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

In the code above, we have both the name and age fields. If we skip any of them, then we’ll get an error from the TypeScript compiler.

If we want to embrace the dynamic typing nature of JavaScript, we can add dynamic index signatures to JavaScript. Also, there’re union and intersection types to combine different types into one.

For instance, we can use it as follows:

interface PersonInterface {
    name: string;
    age: number;
    [key: string]: any;
}

In the code above, we have:

[key: string]: any;

to allow dynamic keys in anything that implements PersonInterface that has anything as a value.

Then we can have any property in addition to name and age in any class that implements PersonInterface or an object that’s cast to the PersonInterface type.

Union types let us join different types together. For instance, we can use it as follows:

interface Person {
    name: string;
    age: number;
}

interface Employee {
    employeeId: string;
}

const staff: Person | Employee = {
    name: 'jane',
    age: 10,
    employeeId: 'abc'
}

In the code above, the | is the union type operator. It lets us combine both the keys from both interfaces into one without creating a new type.

Another good thing about TypeScript is nullable properties. We can make properties optional with the ? operator.

For instance, we can use the following code:

interface Person {
    name: string;
    age?: number;
}

With the ? operator, we made age an optional property.

typeof Operator

Another great feature of TypeScript is the typeof operator, which lets us specify that something has the same type as something else.

For instance, we can use it as follows:

const person = {
    name: 'jane',
    age: 10,
}

const person2: typeof person = {
    name: 'john',
    age: 11,
}

In the code above, we have the person2 object, which has the same type as person since we specified that with typeof person . Then person2 must have the name and age properties or we’ll get an error.

As we can see, we don’t need to specify any interfaces or classes explicitly to specify types. This is handy for getting the types of imported libraries that don’t come with type definitions.

Conclusion

With TypeScript, we made refactoring easy since it’s harder to break the existing code with the type and structure checks that it provides.

It also makes communication easier because we know the type and structure of our objects, classes, and return value of functions.

Categories
TypeScript

An Introduction to TypeScript Interfaces

The big advantage of TypeScript over plain JavaScript is that it extends the features of JavaScript by adding features that ensure type safety of our program’s objects.

It does this by checking the shape of the values that objects take on. Checking the shape is called duck typing or structural typing. Interfaces are one way to fill the role of naming data types in TypeScript.

They are very useful for defining a contract within our code in TypeScript programs.

In the last part, we’ll look at how to define a TypeScript interface and how to add properties to it. We also look at excess property checks for object literals and defining types for interfaces.

In this article, we’ll look at how to extend interfaces and write interfaces that extend classes.


Extending Interfaces

In TypeScript, interfaces can extend each other just like classes. This lets us copy the members of one interface to another and gives us more flexibility in how we use our interfaces.

We can reuse common implementations in different places and we can extend them in different ways without repeating code for interfaces.

We can extend interfaces with the extends keyword. We can use the keyword to extend one or more interfaces separated by commas. For example, we can use the extends keyword as in the code below:

interface AnimalInterface {
  name: string;  
}
interface DogInterface extends AnimalInterface {
  breed: string;
  age: number;
}
interface CatInterface extends AnimalInterface {
  breed: string;
}

Then, to implement the Dog and Cat interfaces, we have to implement the members listed in the Animal interface as well. For example, we would implement them as in the following code:

interface AnimalInterface {
  name: string;  
}
interface DogInterface extends AnimalInterface {
  breed: string;
  age: number;
}
interface CatInterface extends AnimalInterface {
  breed: string;
}
class Cat implements CatInterface {
  name: string = 'Mary';
  breed: string = 'Persian';
}
class Dog implements DogInterface {
  name: string = 'Jane';
  breed: string = 'Labrador';
  age: number = 10;
}

As we can see, we have added everything from the parent interface and the child interface in our class implementations. We can also extend multiple interfaces as in the following code:

interface MachineInterface {
  name: string;  
}
interface ProductInterface {
  price: number;
}
interface ClockInterface extends MachineInterface, ProductInterface {
  tick(): void;
}
class Clock implements ClockInterface {
  name: string = 'Quartz';
  price: number = 20;
  tick() {
    console.log('tick');
  }
}

As we can see from the code above, we have all the members of the Machineinterface, ProductInterface, and ClockInterface if we implement the ClockInterface as we did with the Clock class.

Note that if we have the same member name in multiple interfaces then they must have identical data types as well. For example, if we have the following code:

interface MachineInterface {
  name: string;  
}
interface ProductInterface {
  name: number;
}
interface ClockInterface extends MachineInterface, ProductInterface {
  tick(): void;
}
class Clock implements ClockInterface {
  name: string = 'Quartz';
  price: number = 20;
  tick() {
    console.log('tick');
  }
}

The Typescript compiler would reject it since we have name being a string in the MachineInterface and name being a number in the ProductInterface.

If we try to compile the code above with the TypeScript compiler, we would get the error:

Interface ‘ClockInterface’ cannot simultaneously extend types ‘MachineInterface’ and ‘ProductInterface’. Named property ‘name’ of types ‘MachineInterface’ and ‘ProductInterface’ are not identical.(2320)

Hybrid Types

We can override the type that’s inferred by an object with the type assertion operator, which is denoted by the as keyword in TypeScript.

This way, we can use code that has dynamic types while we keep using the interface. For example, we can write the following code:

interface Person {
  name: string;
  (name: string): string;
}
function getPerson(): Person {
  let person = (function (name: string) { }) as Person;
  person.name = 'Joe';
  return person;
}
let person = getPerson();
person('Joe');

In the code above, we have the person variable in the getPerson function which we set explicitly with the Person type so that we can assign properties listed in the Person interface to the person variable.


Interfaces Extending Classes

TypeScript interfaces can extend classes. This means that an interface can inherit the members of a class but not their implementation. The class, in this case, acts as an interface with all the members declared without providing the implementation.

This means that when we extend a class with private or protected members, the interface can only be implemented by that class or a sub-class of it. For example, we can write an interface that extends a class as we do in the following code:

class Animal {
  name: string = '';
  private age: number = 0;  
}
interface BirdInterface extends Animal {
  breed: string;
  color: string;  
}
class Bird extends Animal implements BirdInterface {
  name: string = 'Bird';    
  breed: string = 'pigeon';
  color: string = 'Gray';
}

In the code above, we first created the class Animal which has a public member name and a private member age. Then, we added a BirdInterface which extends the Animal class by adding the public members breed and color.

Then, in the Bird class, which extends the Animal class and implements the BirdInterface, we have all the members of the BirdInterface plus the public members of the Animal class.

Since private members can’t be accessed outside of a class, we can’t access the member age in the Bird class. We also can’t add another age member in the Bird class.

Otherwise, we would get the errors:

Class ‘Bird’ incorrectly extends base class ‘Animal’. Property ‘age’ is private in type ‘Animal’ but not in type ‘Bird’.(2415)” and “Class ‘Bird’ incorrectly implements interface ‘BirdInterface’. Property ‘age’ is private in type ‘BirdInterface’ but not in type ‘Bird’.(2420)

However, if we change the age member in the Animal class to a protected member, which can be accessed by all sub-classes that extends Animal, then we can reference it in the Bird class as in the following code:

class Animal {
  name: string = '';
  protected age: number = 0;  
}
interface BirdInterface extends Animal {
  breed: string;
  color: string;    
}
class Bird extends Animal implements BirdInterface {
  name: string = 'Bird';    
  breed: string = 'pigeon';
  color: string = 'Gray';
  age: number = 1;
}

This is the same with methods. Private methods can’t be accessed by anything outside the class that it’s defined in and can’t be overridden by any sub-class or interface. For example, if we have the following code:

class Animal {
  name: string = '';
  private age: number = 0;  
  private getAge() {
    return this.age;
  }
}
interface BirdInterface extends Animal {
  breed: string;
  color: string;   
  getAge(): number;
}
class Bird extends Animal implements BirdInterface {
  name: string = 'Bird';    
  breed: string = 'pigeon';
  color: string = 'Gray';  
  getAge() { return 0 };
}

Then we would get the errors:

Class ‘Bird’ incorrectly extends base class ‘Animal’. Property ‘age’ is private in type ‘Animal’ but not in type ‘Bird’.(2415)” and “Class ‘Bird’ incorrectly implements interface ‘BirdInterface’. Property ‘age’ is private in type ‘BirdInterface’ but not in type ‘Bird’.(2420)

However, we can override protected methods in sub-classes as in the following code:

class Animal {
  name: string = '';
  private age: number = 0;  
  protected getAge() {
    return this.age;
  }
}
interface BirdInterface extends Animal {
  breed: string;
  color: string;     
}
class Bird extends Animal implements BirdInterface {
  name: string = 'Bird';    
  breed: string = 'pigeon';
  color: string = 'Gray';  
  getAge() { return 0 };
}

In TypeScript, interfaces can extend each other just like classes. This lets us copy the members of one interface to another and gives us more flexibility in how we use our interfaces.

We can reuse common implementations in different places and we can extend them in different ways without repeating code for interfaces.

Also, TypeScript interfaces can extend classes. This means that an interface can inherit the members of a class but not their implementation. The class, in this case, acts as an interface with all the members declared without providing the implementation.

This means that when we extend a class with private or protected members, the interface can only be implemented by that class or a sub-class of it.