Categories
JavaScript TypeScript

TypeScript Advanced Types — this Type and Dynamic Types

TypeScript has many advanced type capabilities, which makes writing dynamically typed code easy. It also facilitates the adoption of existing JavaScript code since it lets us keep the dynamic capabilities of JavaScript while using the type-checking capability of TypeScript. There are many kinds of advanced types in TypeScript, like intersection types, union types, type guards, nullable types, and type aliases, and more. In this article, we’ll look at the this type and creating dynamic types with index signatures and mapped types.

This Type

In TypeScript, we can use this as a type. It represents the subtype of the containing class or interface. We can use it to create fluent interfaces easily since we know that each method in the class will be returning the instance of a class.

For example, we can use it to define a class with chainable methods like in the following code:

class StringAdder {  
  value: string = '';  
  getValue(): string {  
    return this.value;  
  } 

  addFoo(): this {  
    this.value += 'foo';  
    return this;  
  } 

  addBar(): this {  
    this.value += 'bar';  
    return this;  
  } 

  addGreeting(name: string): this {  
    this.value += `Hi ${name}`;  
    return this;  
  }  
}
const stringAdder: StringAdder = new StringAdder();  
const str = stringAdder  
  .addFoo()  
  .addBar()  
  .addGreeting('Jane')  
  .getValue();  
console.log(str);

In the code above, the addFoo, addBar, and addGreeting methods all return the instance of the StringAdder class, which lets us chain more method calls of the instance to it once it’s instantiated. The chaining is made possible by the this return type that we have in each method.

Index Types

To make the TypeScript compiler check code with dynamic property names, we can use index types. We can use the extends keyof keyword combination to denote that the type has the property names of another type. For example, we can write:

function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {  
  return propNames.map(n => o[n]);  
}

Then we can use the choose function as in the following code:

function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {  
  return propNames.map(n => o[n]);  
}

const obj = {  
  a: 1,  
  b: 2,  
  c: 3  
}  
choose(obj, ['a', 'b'])

Then we get the values:

[1, 2]

if we log the results of the choose function. If we pass in a property name that doesn’t exist in the obj object into the array in the second of the choose function, then we get an error from the TypeScript compiler. So if we write something like the following code:

function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {  
  return propNames.map(n => o[n]);  
}const obj = {  
  a: 1,  
  b: 2,  
  c: 3  
}  
const arr = choose(obj, ['d']);

Then we get the error:

Type 'string' is not assignable to type '"a" | "b" | "c"'.(2322)

In the examples above, keyof U is the same as the string literal type “a” | “b” | “c” since we passed in the type of the generic U type marker where the actual type is inferred from the object that we pass in into the first argument. The K extends keyof U part means that the second argument must have an array of some or all the key names of whatever is passed into the first argument, which we denoted by the generic U type. Then we defined the return type as an array of values that we get by looping through the object we pass into the first argument, hence we have the U[K][] type. U[K][] is also called the index access operator.

Index types and Index Signatures

An index signature is a parameter that must be of type string or number in a TypeScript interface. We can use it to denote the properties of a dynamic object. For example, we can use it like we do in the following code:

interface DynamicObject<T> {  
  [key: string]: T;  
}  
let obj: DynamicObject<number> = {  
  foo: 1,  
  bar: 2  
};  
let key: keyof DynamicObject<number> = 'foo';  
let value: DynamicObject<number>['foo'] = obj[key];

In the code above, we defined a DynamicObject<T> interface which takes a dynamic type for its members. We have an index signature called the key which is a string. It can also be a number. The type of the dynamic members is denoted by T, which is a generic type marker. This means that we can pass in any data type into it.

Then we defined the obj object, which is of type DyanmicObject<number>. This makes use of the DynamicObject interface we created earlier. Then we defined the key variable, which has the type keyof DynamicObject<number>, which means that it has to be a string or a number. This means that the key variable must have one of the property names as the value. Then we defined the value variable, which must have the value of an object of type DynamicObject .

This means that we can’t assign anything other than a string or number to the key variable. So if write something like:

let key: keyof DynamicObject<number> = false;

Then we get the following error message from the TypeScript compiler:

Type 'false' is not assignable to type 'string | number'.(2322)

Mapped Types

We can create a new type by mapping the members of an existing type into the new type. This is called a mapped type.

We can create mapped types like we do in the following code:

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

type ReadOnly<T> = {  
  readonly [P in keyof T]: T[P];  
}

type PartialType<T> = {  
  [P in keyof T]?: T[P];  
}

type ReadOnlyPerson = ReadOnly<Person>;  
type PartialPerson = PartialType<Person>;let readOnlyPerson: ReadOnlyPerson = {  
  name: 'Jane',  
  age: 20  
}
readOnlyPerson.name = 'Joe';  
readOnlyPerson.age = 20;

In the code above, we created the ReadOnly type alias to let us map the members of an existing type into a new type by setting each member of the type as readonly. This isn’t a new type on its own since we need to pass in a type to the generic type marker T . Then we create an alias for the types that we defined by passing in the Person type into the ReadOnly alias and Partial alias respectively.

Next we defined a ReadOnlyPerson object with the name and age properties set. Then when we try to set the values again, then we get the following errors:

Cannot assign to 'name' because it is a read-only property.(2540)Cannot assign to 'age' because it is a read-only property.(2540)

Which means that the readonly property from the ReadOnly type alias is being enforced. Likewise, we can do the same with the PartialType type alias. We have defined the PartialPerson type by mapping the members of the Person type to the PartialPerson type with the PartialPerson type. Then we can define a PartialPerson object like in the following code:

let partialPerson: PartialPerson = {};

As we can see, we can omit properties from the partialPerson object we as want.

We can add new members to the mapped type alias by creating an intersection type from it. Since we used the type keyword to define the mapped types, they’re actually actually types. They are actually type aliases. This means that we can’t put members straight inside, even though they look like interfaces.

To add members, we can write something like the following:

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

type ReadOnly<T> = {  
  readonly [P in keyof T]: T[P];  
}

type ReadOnlyEmployee = ReadOnly<Person> & {  
  employeeCode: string;  
};

let readOnlyPerson: ReadOnlyEmployee = {  
  name: 'Jane',  
  age: 20,  
  employeeCode: '123'  
}

Readonly<T> and Partial<T> are included in the TypeScript standard library. Readonly maps the members of the type that we pass into the generic type placeholder into read-only members. The Partial keyword lets us map members of a type, into nullable members.

Conclusion

In TypeScript, we can use this as a type. It represents the subtype of the containing class or interface. We can use it to create fluent interfaces easily since we know that each method in the class will be returning the instance of a class. An index signature is a parameter that must be of type string or number in a TypeScript interface.

We can use it to denote the properties of a dynamic object. To convert members of a type to add some attributes to them, we can map the members of an existing type into the new type to add the attributes to the interface with mapped types.

Categories
JavaScript Rxjs

Some Useful Rxjs Transformation Operators

RxJS is a library for reactive programming. Creating operators are useful for generating data from various data sources to be subscribed to by Observers.

In this article, we’ll look at some RxJS transformation operators like bufferTime, bufferToggle, bufferWhen, and concatMap operators.

bufferTime

The bufferTime operator buffers the emitted data of the originating Observable for a specific time period.

It takes one argument, the bufferTimeSpan, which is the amount of time to fill each buffer array. The unit for the time span is milliseconds.

The buffer data is emitted and the buffer is reset once the amount of time specified is up.

It also takes a bufferCreationInterval argument which specifies the time span for which the buffer data builds up. The operator opens the buffer every bufferCreationInterval milliseconds and emits and resets every bufferTimeSpan milliseconds.

Another optional argument for this operator is the maxBufferSize, which is a number that specifies the maximum size of items buffered.

For example, we can use it as follows:

import { interval } from "rxjs";  
import { bufferTime } from "rxjs/operators";

const clicks = interval(2000);  
const buffered = clicks.pipe(bufferTime(1000));  
buffered.subscribe(x => console.log(x));

We should see a new number emitted every 2 seconds in the arrays that are emitted, while the rest of the emitted arrays are empty.

The bufferCreationInterval argument can be used as follows:

import { interval } from "rxjs";  
import { bufferTime } from "rxjs/operators";

const clicks = interval(2000);  
const buffered = clicks.pipe(bufferTime(1000, 1000));  
buffered.subscribe(x => console.log(x));

In the code above, the buffered data is emitted every second, and the buffer is created every second.

bufferToggle

bufferToggle buffers the source Observable values starting from the emission of openings and ending when the output of the closingSelector emits.

Values emitted by the originating Observable is buffered until the closingSelector tells us to stop emitting values from the originating Observable.

For example, if we have a button as follows:

<button>Click Me</button>

We can buffer the mouse click events on a button and the emit the MouseEvent objects that were buffered according to the closingSelector function’s specification as follows:

import { fromEvent, interval, EMPTY } from "rxjs";  
import { bufferToggle } from "rxjs/operators";

const clicks = fromEvent(document.querySelector("button"), "click");  
const openings = interval(1000);  
const buffered = clicks.pipe(  
  bufferToggle(openings, i => (i % 2 ? interval(1500) : EMPTY))  
);  
buffered.subscribe(x => console.log(x));

The closingSelector in the example above is:

(i % 2 ? interval(1500) : EMPTY)

The code will emit data from the openings Observable when it starts emitting, which is when we click on our button, ever 1.5 seconds. The emitted data is buffered into an array and then the buffering ends when i % 2 is false, and the interval(1500) Observable is returned signaling closure. It’ll continue buffer when EMPTY is emitted.

bufferWhen

The bufferWhen operator buffers data from the source Observable until the closingSelector function closes the buffer.

It takes one argument, which is the closingSelector function to specify when the buffer will be closed.

For example, we can use it as we do in the following code:

import { fromEvent, interval } from "rxjs";  
import { bufferWhen } from "rxjs/operators";

const clicks = fromEvent(document.querySelector("button"), "click");  
const buffered = clicks.pipe(  
  bufferWhen(() => interval(1000 + Math.random() * 4000))  
);  
buffered.subscribe(x => console.log(x));

What we did is get the button clicks and then emit the MouseEvent object array of the MouseEvent s from the clicks that are buffered.

Once we did, then the closingSelector specifies that we emit the buffered values every 1000 + Math.random() * 4000 milliseconds and empty the buffer and buffer the click events again.

concatMap

The concatMap operator takes each source value of the originating Observable and wait for one value of the originating Observable to emit before the next emitted value from it emits.

It takes 2 arguments. The first is a project function, which is a function that returns a new Observable with operations that we want to be applied to the values emitted from the originating Observable. The second argument is the resultSelector . It’s an optional argument, which is a function to pick the emitted values that we want to emit in the returned Observable.

For example, we can use it as follows:

import { fromEvent, interval, of } from "rxjs";  
import { concatMap, take } from "rxjs/operators";

const clicks = fromEvent(document, "click");  
const result = clicks.pipe(concatMap(ev => interval(1000).pipe(take(5))));  
result.subscribe(x => console.log(x));

The code above will receive the click events, then after 1 second emit 0, then after another second emit 1, up until it reaches 4.

It’ll do the same thing each time we click.

The bufferTime operator buffers the emitted data of the originating Observable for a specific time period, then the buffered data will be emitted as an array.

bufferToggle buffers the source Observable values starting from the emission of openings and ending when the output of the closingSelector function emits.

The bufferWhen operator buffers data from the source Observable until the closingSelector function emits its data.

Finally the concatMap operator takes each source value of the originating Observable and waits for one value of the originating Observable to emit before the next emitted value from it emits.

Categories
JavaScript Nodejs

Node.js FS Module — Symbolic Links and Timestamps

Manipulating files and directories are basic operations for any program. Since Node.js is a server-side platform and can interact with the computer that it’s running on directly, being able to manipulate files is a basic feature.

Fortunately, Node.js has a fs module built into its library. It has many functions that can help with manipulating files and folders. File and directory operations that are supported include basic ones like manipulating and opening files in directories. Likewise, it can do the same for files.

It can do this both synchronously and asynchronously. It has an asynchronous API that has functions that support promises.

Also, it can show statistics for a file. Almost all the file operations that we can think of can be done with the built-in fs module.

In this article, we will create symbolic links with the symlink family of functions and set timestamps with the utimes family of functions.

Creating Symbolic Links

Symbolic links are files that reference other files in the form of relative or absolute paths of the file. We can create symbolic links in Node.js programs with the symlink function.

The function takes 4 arguments.

The first argument is the target path for our symbolic link which is the file path that we want to reference in our symbolic link. It can be in the form of a string, a buffer object, or an URL object.

The second argument is the path of the symbolic link, it can also be in the form of a string, a buffer object, or an URL object.

The third argument is the type, which is a string. It’s only available on Windows and ignored on other platforms. The possible values are 'dir' , 'file' , or 'junction' .

If the type argument isn’t set, it will automatically detect the type of the target and use 'file' or 'dir' . If the target doesn’t exist, then 'file' would be used. Windows requires that the symbolic link path to be absolute. When using 'junction' , the target argument will be converted to the absolute path.

The last argument is a callback function that’s called when the symbolic link creation operation ends. It has an err parameter which is null when the operation succeeds and has an object with the error information when it failed.

We can create a symbolic link with the symlink function as follows:

const fs = require("fs");  
const target = "./files/file.txt";  
const path = "./files/symlink";

fs.symlink(target, path, "file", err => {  
  if (err) {  
    throw err;  
  }  
  console.log("Symbolic link creation complete!");  
});

After running the code above, when we run stat ./files/symlink on POSIX systems, we should get output that looks something like the following:

File: './files/symlink' -> './files/file.txt'  
  Size: 16              Blocks: 0          IO Block: 512    symbolic link  
Device: eh/14d  Inode: 62487444831945583  Links: 1  
Access: (0777/lrwxrwxrwx)  Uid: ( 1000/hauyeung)   Gid: ( 1000/hauyeung)  
Access: 2019-11-03 11:22:19.787359800 -0800  
Modify: 2019-11-03 11:22:19.787359800 -0800  
Change: 2019-11-03 11:22:19.787359800 -0800  
 Birth: -

This means that are symbolic link has been successfully created.

There’s also a synchronous version of the symlink function that’s called the symlinkSync function. It takes 3 arguments.

The first argument is the target path for our symbolic link which is the file path that we want to reference in our symbolic link. It can be in the form of a string, a buffer object, or an URL object.

The second argument is the path of the symbolic link, it can also be in the form of a string, a buffer object, or an URL object. The third argument is the type, which is a string. It’s only available on Windows and ignored on other platforms.

The possible values are 'dir' , 'file' , or 'junction' . If the type argument isn’t set, it will automatically detect the type of the target and use 'file' or 'dir' . If the target doesn’t exist, then 'file' would be used. Windows requires that the symbolic link path to be absolute.

When using 'junction' , the target argument will be converted to the absolute path. It returns undefined .

We can use it to create symbolic links like in the following code:

const fs = require("fs");  
const target = "./files/file.txt";  
const path = "./files/symlink";

try {  
  fs.symlinkSync(target, path, "file");  
  console.log("Symbolic link creation complete!");  
} catch (error) {  
  console.error(error);  
}

After running the code above, when we run stat ./files/symlink on POSIX systems, we should get output that looks the same as the ones above.

There’s also a promise version of the symlink function. It takes 3 arguments.

The first argument is the target path for our symbolic link which is the file path that we want to reference in our symbolic link. It can be in the form of a string, a buffer object, or an URL object.

The second argument is the path of the symbolic link, it can also be in the form of a string, a buffer object, or an URL object.

The third argument is the type, which is a string. It’s only available on Windows and ignored on other platforms.

The possible values are 'dir' , 'file' , or 'junction' . If the type argument isn’t set, it will automatically detect type of the target and use 'file' or 'dir' . If the target doesn’t exist, then 'file' would be used.

Windows requires that the symbolic link path be absolute. When using 'junction' , the target argument will be converted to the absolute path. It returns a promise that resolves with no arguments when it’s successful.

We can use it to create symbolic links like in the following code:

const fsPromises = require("fs").promises;  
const target = "./files/file.txt";  
const path = "./files/symlink";

(async () => {  
  try {  
    await fsPromises.symlink(target, path, "file");  
    console.log("Symbolic link creation complete!");  
  } catch (error) {  
    console.error(error);  
  }  
})();

After running the code above, when we run stat ./files/symlink on POSIX systems, we should get output that looks the same as the ones above.

The promise version of the symlink function is a much better choice than the symlinkSync function when you want to do multiple things sequentially that includes a call to the symlink function since it doesn’t tie up the whole program waiting for the symbolic link creation operation to complete before continuing to program other parts of the program.

Changing Timestamps of Items Stored on Disk

We can change the timestamp of the last accessed time and the last time a file was modified with the utimes function.

The function takes 4 arguments.

The first is the path of the object stored on disk. It can be a string, a Buffer object, and an URL object.

The second argument is the atime which is the time that the object was last accessed. It can be a number, a string or a Date object.

If it’s a number, then it should be the UNIX timestamp.

If it’s a string, then it should be a string form of the UNIX timestamp.

If the value can’t be converted to a number or is NaN, Infinity or -Infinity then an error will be thrown.

The third argument is the mtime , which is the time that the object was last modified. It can be a number, a string or a Date object. It should be in the same format as the atime argument.

The fourth argument is a callback function which takes an err parameter. It’s null when the operation succeeds and has an object that has the error information if it fails.

We can use the utimes function like in the following code:

const fs = require("fs");  
const path = "./files/file.txt";

fs.utimes(  
  path,  
  new Date(2019, 0, 1, 0, 0, 0, 0),  
  new Date(2019, 0, 1, 0, 0, 0, 0),  
  err => {  
    if (err) {  
      throw err;  
    }  
    console.log("Timestamps changed");  
  }  
);

If we run the code above then run stat ./files/file.txt, we should get something like the following output:

File: './files/file.txt'  
  Size: 16              Blocks: 0          IO Block: 512    regular file  
Device: eh/14d  Inode: 22799473115106242  Links: 1  
Access: (0777/-rwxrwxrwx)  Uid: ( 1000/hauyeung)   Gid: ( 1000/hauyeung)  
Access: 2019-01-01 00:00:00.000000000 -0800  
Modify: 2019-01-01 00:00:00.000000000 -0800  
Change: 2019-11-03 11:41:16.155815100 -0800  
 Birth: -

As we can see, the Access and Modify times changed to 2019–01–01 00:00:00.000000000 , so we know that the utimes function has changed the timestamps successfully.

There’s a synchronous version of the utimes function called the utimesSync function.

The function takes 3 arguments.

The first is the path of the object stored on disk. It can be a string, a Buffer object and an URL object. The second argument is the atime which is the time that the object was last accessed.

It can be a number, a string or a Date object. If it’s a number, then it should be the UNIX timestamp. If it’s a string, then it should be a string form of the UNIX timestamp. If the value can’t be converted to a number or is NaN, Infinity or -Infinity then an error will be thrown.

The third argument is the mtime , which is the time that the object was last modified. It can be a number, a string or a Date object. It should be in the same format as the atime argument.

We can use it as in the following code:

const fs = require("fs");  
const path = "./files/file.txt";

try {  
  fs.utimesSync(  
    path,  
    new Date(2019, 0, 1, 0, 0, 0, 0),  
    new Date(2019, 0, 1, 0, 0, 0, 0)  
  );  
  console.log("Timestamps changed");  
} catch (error) {  
  console.error(error);  
}

If we run the code above then run stat ./files/file.txt, we should get the same output as we did above.

There’s also a promise version of the utimes function, which let us run utimes asynchronously while running it in sequence like we do with the utimesSync function.

The function takes 3 arguments. The first is the path of the object stored on disk. It can be a string, a Buffer object, and an URL object.

The second argument is the atime which is the time that the object was last accessed. It can be a number, a string or a Date object. If it’s a number, then it should be the UNIX timestamp.

If it’s a string, then it should be a string form of the UNIX timestamp. If the value can’t be converted to a number or is NaN, Infinity or -Infinity then an error will be thrown.

The third argument is the mtime , which is the time that the object was last modified. It can be a number, a string or a Date object. It should be in the same format as the atime argument.

We can use it as in the following code:

const fsPromises = require("fs").promises;  
const path = "./files/file.txt";

(async () => {  
  try {  
    await fsPromises.utimes(  
      path,  
      new Date(2019, 0, 1, 0, 0, 0, 0),  
      new Date(2019, 0, 1, 0, 0, 0, 0)  
    );  
    console.log("Timestamps changed");  
  } catch (error) {  
    console.error(error);  
  }  
})();

If we run the code above then run stat ./files/file.txt, we should also get the same output as the regular utimes example.

We can create symbolic links with the symlink family of functions.

It takes 3 arguments. The first is the path of the object stored on disk. It can be a string, a Buffer object, and an URL object.

The second argument is the atime which is the time that the object was last accessed. It can be a number, a string or a Date object. If it’s a number, then it should be the UNIX timestamp, and if it’s a string, then it should be a string form of the UNIX timestamp.

The regular asynchronous version also takes a callback function which runs when it ends. The utimes function changes the timestamp of the access and modified time of an object stored on disk. It takes the path of the entity to apply the change and timestamps for access and modified times.

The regular asynchronous version also takes a callback function which runs when it ends. They are handy for times when we need to do these operations in our Node.js programs.

Categories
Flow JavaScript

JavaScript Type Checking with Flow — Generics

ow is a type checker made by Facebook for checking JavaScript data types. It has many built-in data types we can use to annotate the types of variables and function parameters.

In this article, we’ll look at how to use generic types to make data types abstract and allows reuse of the code.

Defining Generic Types

To make the types abstract in different entities, we can use generic types to abstract away the types from entities.

For example, we can write a function with generic types as follows:

function foo<T>(obj: T): T {  
  return obj;  
}

In the code above, we have <T> to indicate that foo is a generic function. T is the generic type marker whenever it’s referenced.

To make use of generic types, we have to annotate it. Otherwise, Flow wouldn’t know that it can take a generic type.

We have to annotate the type in type aliases when we want to generic type in type aliases.

For example, we can write:

type Foo = {  
  func<T>(T): T  
}

Then it’ll fail if we write:

type Foo = {  
  func<T>(T): T  
}function foo(value) {  
  return value;  
}const f: Foo = { func: foo };

Since we didn’t add generic type markers to our foo function.

Once we annotate our foo function with generic types, it should work:

type Foo = {  
  func<T>(T): T  
}function foo<T>(value: T): T {  
  return value;  
}const f: Foo = { func: foo };

Syntax

Generic Functions

We can define a generic function as follows:

function foo<T>(param: T): T {  
  
}  
  
function<T>(param: T): T {  
  
}

Also, we can define function types with generics as follows:

<T>(param: T) => T

We can use generic function types with variables and parameters like:

let foo: <T>(param: T) => T = function(param: T): T {}function bar(callback: <T>(param: T) => T) {  
    
}

Generic Classes

To create generic classes, we can insert type placeholders into fields, method parameters, and the return type of methods.

For instance, we can define a generic class as follows:

class Foo<T> {  
  prop: T; constructor(param: T) {  
    this.prop = param;  
  } bar(): T {  
    return this.prop;  
  }  
}

Type Aliases

Generic types can also be added to type aliases. For example, we can write:

type Foo<T> = {  
  a: T,  
  v: T,  
};

Interfaces

Likewise, we can define interfaces with generics as follows:

interface Foo<T> {  
  a: T,  
  b: T,  
}

Passing in Type Arguments

For functions, we can pass in type arguments as follows:

function foo<T>(param: T): T {    
  return param;  
}foo<number>(1);

We can pass in type arguments to classes as well. When we instantiate a class, we can pass in a type argument as follows:

class Foo<T> {}  
const c = new Foo<number>();

One convenient feature of Flow generics is that we don’t have to add all the types ourselves. We can put an _ in place of a type to let Flow infer the type for us.

For instance, we can write the following:

class Foo<T, U, V>{}  
const c = new Foo<_, number, _>()

To let Flow infer the type of T and V.

Behavior

Generic are variables for types. We can use them in place of any data type annotations in our code.

Also, we can name them anything we like, so we can write something like:

function foo<Type1, Type2, Type3>(one: Type1, two: Type2, three: Type3) {  
    
}

Flow tracks the values of variables and parameters annotated with generic types so we can assign something unexpected to it.

For example, we’ll get an error if we write something like:

function foo<T>(value: T): T {    
  return "foo";  
}

Since we don’t know that if T is a string, we can always return a string when foo is called.

Also, Flow tracks the type of value we pass through a generic so we can use it later:

function foo<T>(value: T): T {  
  return value;  
}

let one: 1 = foo(1);

Notice that we didn’t pass in any generic type argument for it to identify the type as 1. Changing 1 to number also works:

let one: number = foo(1);

If we omit the generic type argument in a generic function, Flow will let us pass in anything:

function logBar<T>(obj: T): T {  
  if (obj && obj.bar) {  
    console.log(obj.bar);  
  }  
  return obj;  
}

logBar({ foo: 'foo', bar: 'bar' });    
logBar({ bar: 'bar' });

We can restrict what we can pass in by specifying restrictions for the type parameter:

function logBar<T: { bar: string }>(obj: T): T {  
  if (obj && obj.bar) {  
    console.log(obj.bar);  
  }  
  return obj;  
}

logBar({ foo: 'foo', bar: 'bar' });    
logBar({ bar: 'bar' });

After adding { bar: string } , then we know that anything passed in must have the stringbar property.

We can do the same for primitive values:

function foo<T: number>(obj: T): T {  
  return obj;  
}

foo(1);

If we try to pass in data of any other type, it’ll fail, so

foo('2');

will get us an error.

Generics lets us return a more specific type than we specify in the type parameter. For example, if we have a string function that returns the value that’s passed in:

function foo<T: string>(val: T): T {  
  return val;  
}

let f: 'foo' = foo('foo');

Instead of assigning something to a string variable, we can assign something that has the returned value of the function as the type.

Parameterized Generics

We can pass in types to generics like we pass arguments to a function. We can do this with type alias, functions, interfaces, and classes. This is called a parameterized generic.

For example, we can make a parameterized generic type alias as follows:

type Foo<T> = {  
  prop: T,  
}

let item: Foo<string> = {  
  prop: "value"  
};

Likewise, we can do the same for classes:

class Foo<T> {  
  value: T;  
  constructor(value: T) {  
    this.value = value;  
  }  
}

let foo: Foo<string> = new Foo('foo');

For interfaces, we can write:

interface Foo<T> {  
  prop: T,  
}

class Item {  
  prop: string;  
}(Item.prototype: Foo<string>);

Default Type for Parameterized Generics

We can add default values to parameterized generics. For example, we can write:

type Item<T: string = 'foo'> = {  
  prop: T,  
};

let foo: Item<> = { prop: 'foo' };

If the type isn’t specified, then prop is assumed to have the 'foo' type.

That means that any other value for prop won’t work if we leave the type argument blank. So something like:

let foo: Item<> = { prop: 'bar' };

won’t be accepted.

Variance Sigils

We can use the + sign to allow for broader types than the assigned value’s type when the casting types of generics. For example, we can write:

type Foo<+T> = T;let x: Foo<string> = 'foo';  
(x: Foo<number| string>);

As we can see, we can convert x from type Foo<string> to Foo<number| string> with the + sign on the generic Foo type.

With generics, we can abstract the type out of our code by replacing them with generic type markers. We can use them anywhere that has type annotations. Also, it works with any Flow constructs like interfaces, type alias, classes, variables, and parameters.

Categories
JavaScript TypeScript

Introduction to TypeScript Classes — More Access Modifiers

Classes in TypeScript, like JavaScript are a special syntax for its prototypical inheritance model that is a comparable inheritance in class-based object oriented languages. Classes are just special functions added to ES6 that are meant to mimic the class keyword from these other languages. In JavaScript, we can have class declarations and class expressions, because they are just functions. So like all other functions, there are function declarations and function expressions. This is the same with TypeScript. Classes serve as templates to create new objects. TypeScript extends the syntax of classes of JavaScript and then add its own twists to it. In this article, we’ll look at more access modifiers for class members in TypeScript.

Readonly modifier

With TypeScript, we can mark a class member as read only with the readonly keyword. This prevents a member from being modified once it’s been initialized. Also, they must be initialized at their declaration or in the constructor. For example, we can use it like in the following code:

class Person {  
  readonly name: string;  
  constructor(name: string) {  
    this.name = name;        
  }  
}

const person = new Person('Jane');

If we try assign another to it after a value has been set to name after initialization, then we would get an error. For example, if we write:

class Person {  
  readonly name: string;  
  constructor(name: string) {  
    this.name = name;        
  }  
}  
const person = new Person('Jane');  
person.name = 'Joe';

Then the TypeScript compiler won’t compile the code and we would get the error message “Cannot assign to ‘name’ because it is a read-only property.(2540)“

We can make the code above shorter by using parameter properties. With parameter properties, we can both declare a readonly member and assign it a value by just putting the member declaration in the signature of the constructor. Once we put it inside the parentheses, then we can both declare it and assign it a value at the same time. For example, instead of writing:

class Person {  
  readonly name: string;  
  constructor(name: string) {  
    this.name = name;        
  }  
}

const person = new Person('Jane');

We can instead write:

class Person {    
  constructor(readonly name: string) {      
  }  
}

const person = new Person('Jane');

Then when we run console.log on person , then we can see that the member name has been assigned the value ‘Jane’ without having to explicitly write code to assign it the value. We can also replace readonly , with public , private , or protected ; or combine readonly with public , private , or protected . For example, we can write the following code:

class Person {    
  constructor(private readonly name: string) {      
  } getName() {  
    return this.name;  
  }  
}  
const person = new Person('Jane');  
console.log(person.getName());

to get a private and readonly member called name , which retrieve the value of it in the getName method. We should get ‘Jane’ when we run the console.log line on the last line. Likewise, we can do the same with public or protected like in the following code:

class Person {    
  constructor(protected readonly name: string) {      
  }  
}

class Employee extends Person {    
  constructor(  
    public name: string,   
    private employeeCode: number  
  ){      
    super(name);  
  } 

  getEmployeeCode() {  
    return this.employeeCode;  
  }  
}  
const person = new Employee('Jane', 123);  
console.log(person.name);  
console.log(person.getEmployeeCode());

As we can see, we have parameter properties that with all kinds of access modifiers and the readonly keyword in both the Person and the Employee class. Then we can use the Employee constructor to assign all the values to all the members with the Employee constructor. Then when we get the values of the members either directly or through a method as in the case of the private employeeCode member, where we retrieved it through the getEmployeeCode method, then we can see that the values we expected are logged. We see that person.name is ‘Jane’, and person.getEmployeeCode() gets us 123.

Accessors

Like in JavaScript, we can add getter and setter methods into TypeScript classes. This prevents us from accidentally modifying public member values directly, and gives us more control for how member values are retrieved and set. To add getters and setters to a class, we can use the get and set keywords respectively. We put them in front of the method signature of the class to designate a method as a getter or a setter. For example, we can use it like in the following code:

class Person {  
  private _name: string = ''; get name(): string {  
    return this._name;          
  } 

  set name(newName: string) {  
    if (newName && newName.length < 5) {  
      throw new Error('Name is too short');        
    }  
    this._name = newName;  
  }  
}

let person = new Person();  
person.name = 'Jane Smith';  
console.log(person.name);  
person.name = 'Joe';

In the example above, we added a getter method with the get keyword. The name method is used for getting the _name field, which is private, so we can’t get the value of it without a getter method. To retrieve the value of this._name via our getter name method, we just use the person.name property to get it. Then we to set the value of this._name , we add a setter method with the set keyword and the method name name . In the name setter method, we pass in the parameter which let us assign a value to it with the assignment operator like we did in the third last line in the code above.

As we can see, we can put validation code in the set name method. This is one good reason to use getter and setter methods because we can control how values are set for individual class members. In the example above, if the value we assign has less than 5 characters, then we throw an errors which has the message ‘Name is too short’. This prevents us from assigning a value where the string is less than 5 characters. If we run the code, the first assignment expression:

person.name = 'Jane Smith';

Then we get ‘Jane Smith’ logged. When we try to assign it a value that has less than 5 characters like we did with:

person.name = 'Joe';

Then we get an error raised like we have indicated in the code.

Note that to use accessors, we have to compile our output to ES5 or higher. Compiling to ES3 isn’t supported, but this should be a problems with modern browsers. Also, accessors that has a get but no set are automatically inferred as readonly .

In TypeScript, we have the readonly modifier for class members so that they won’t be able to be set to a new value after they have been initialized. Also, TypeScript has the parameter properties features so that we don’t have to write code explicitly to assign values to variables via the constructor. If we add in the parameters in the constructor, then they’ll be set automatically when we instantiate the class with the new keyword. The accessor methods are useful for controlling how we get and set values of class members. We can designate getter and setter methods with the get and set keywords.