12. Appendix: Syntax Examples
ECMAScript 6 Features
Optional/Default parameters
// "final" and "interval" parameters are optional.
// If omitted, they will be defaulted to 0 and 1 (respectively)
function countdown(initial, final = 0, interval = 1) {
// Countdown logic
}
// You can then call the method with one, two, or three parameters:
countdown(10) // -> countdown(10, 0, 1)
countdown(10, -10) // -> countdown(10, -10, 1)
countdown(10, -10, 5)
Template strings
var name = 'Ada Lovelace';
var birthDate = new Date(1815, 12, 10);
var template = `
<div class="user">
<p class='name'>${name}</p>
<p>
<span class='birthDate'>${birthDate.toDateString()}</span>
<span class='age'>
${ Math.floor( (new Date() - birthDate) / 31536000000) }
years old
</span>
</p>
</div>
`;
let and const
let
Use let anywhere that you previously used var, especially in places where you wouldn’t expect variables to creep out of their scope, such as:
for(var x = 0; x <= 5; x++) {
var counter = x;
}
console.log(counter); // Prints 5... how odd!
Instead, with let:
for(let x = 0; x <= 5; x++) {
let counter = x;
}
console.log(counter); // Browser says "I don't know about 'counter'" (like you'd\
expect!)
const
for(let x = 0; x < 5; x++) {
const counter = x; // Assign a constant to an initial value...
counter = 1; // Try to reassign and get a runtime error, "can't reass\
ign constant variable"
}
For..of loops
Use for/of anywhere that you previously would have used for/in:
var array = [ "Pick up drycleaning", "Clean Batcave", "Save Gotham" ];
// Old way:
for(var index in array) {
var value = array[index];
console.log(value);
}
// New way:
for(var value of array) {
console.log(value);
}
Arrow Functions
Use arrow functions almost anywhere that you would have used an anonymous function:
var todos = [
{ name: "Pick up drycleaning", completed: false },
{ name: "Clean Batcave", completed: true },
{ name: "Save Gotham", completed: false }
];
// Old way:
var completed = todos.filter(function(todo) {
return todo.completed;
});
// New way:
var completed = todos.filter((todo) => { return todo.completed; });
// Of course, the parentheses around the parameter list are optional if there \
is only one parameter:
var completed = todos.filter(todo => { return todo.completed; });
// And if you've only got one line/expression, you don't even need the bracket\
s.
// (Bonus: the return value is the result of the expression!)
var completed = todos.filter(todo => todo.completed);
Caveat: the this keyword
The only time arrow functions actually change the functionality of a function is by saving a reference to the this keyword. Often, this is exactly what you want to happen!
function CounterButton(element) {
this.counter = 0;
element.addEventListener('click', function() {
this.counter += 1; // Doesn't work :(
})
// Works great!
element.addEventListener('click', () => this.counter += 1);
}
Destructuring
Assigning a set of variables from a simple array:
var array = [123, "Pick up drycleaning", false];
var [id, title, completed] = array; // -> id = 123, title = "Pick up drycle\
aning", completed = false
Swapping the values of variables:
var a = 1;
var b = 5;
[a, b] = [b, a]; // -> a = 5, b = 1
Assigning a set of variables from an object:
var todo = {
id: 123,
title: "Pick up drycleaning",
completed: false
};
var { id, title, completed } = todo; // -> id = 123, title = "Pick up drycl\
eaning", completed = false
Assigning a set of variables from an object where the target variable name is different from the object property name:
var { completed: isCompleted, title, id } = todo; // -> id = 123, title = "\
Pick up drycleaning", isCompleted = false
You can even set default values:
var todo { name: 'Pick up drycleaning' };
var { name, isCompleted = false } = todo; // -> name = 'Pick up drycleaning'\
, isCompleted = false
And, perhaps the coolest usage: destructing a function parameter object to a set of variables in the function (with default values!):
function countdown({ initial, final: final = 0, interval: interval = 1, initia\
l: current }) {
// Countdown logic...
}
countdown({ initial: 20 }) // -> initial = 20, fina\
l = 0, interval = 1, current = 20
countdown({ initial: 20, final: -10 }) // -> initial = 20, fina\
l = -10, interval = 1, current = 20
countdown({ initial: 20, final: -10, interval: 5 }) // -> initial = 20, fina\
l = -10, interval = 5, current = 20
The spread operator
Use the spread operator (...) to accept any number of function parameters into a single array variable:
function add(...values) {
return values.reduce( (a, b) => a + b );
}
The function can still have other parameters, as long as the spread parameter comes last:
function calculate(action, ...values) {
switch (action) {
case 'add':
return values.reduce( (a, b) => a + b );
case 'subtract':
return values.reduce( (a, b) => a - b );
default:
throw `Unknown action ${action}!`;
}
}
Or, use it to concatenate arrays together:
var source = [ 3, 4, 5 ];
var target = [ 1, 2, ...source , 6, 7 ]; // -> [ 1, 2, 3, 4, 5, 6, 7 ]
Computed properties
Define property names on the fly:
const osPrefix = 'os_';
var support = {
[osPrefix + 'Windows']: false,
[osPrefix + 'iOS']: true,
[osPrefix + 'Android']: true,
};
support // -> { os_Windows: false, os_iOS: true, os_Android: true }
## Fundamentals
Specifying a type
Declaring the type of a variable:
var name: string;
Or, using type inference, just assign it to a string!
var name = ‘Jess’;
Or, if you really want to be explicit, do both:
var name: string = ‘Jess’;
Alternatively, if you want to completely and explictly opt out of typing, you can specify the any type:
var name: any;
Of course, function parameters are just variables, so you can use the same syntax there, too:
function sayName(name: string) { }
And speaking of functions, use the same syntax to identify the function’s return type:
function add(x: number, y: number): number { return x + y; }
Union Types
Want to accept multiple different types for the same parameter?
No problem (as long as they share the same base type… like object for instance).
You can even access any properties that they both share!
function getLength(x: (string | any[])): number { return x && x.length; }
Function overloads
Many people prefer the function overload option to define a function that accepts multiple different parameter types, like this:
function getLength(x: string): number function getLength(x: any[]): number function getLength(x: any) : number { return x && x.length; }
Custom Types
TypeScript is all about types - heck, it’s even in the name!
In addition to the standard JavaScript types, there are multiple ways to define your own custom types.
Interface
The TypeScript syntax to define an interface should be incredibly familiar to anyone who’s used C, C++, C#, or Java:
interface IAnimal {
// Can have properties:
name: string;
birthYear: number;
// Or methods:
getAge(): number;
}
Enum
The TypeScript syntax to define an enum should be incredibly familiar to anyone who’s used C, C++, C#, or Java:
enum Gender { Female, Male, Other }
You can even give them custom values:
enum Gender { Female = 20, Male = 30, Other // Guess what value this is? (Hint: Male + 1) }
Once you’ve defined an enum, you can access its numeric value or its textual value:
Colors.Female // -> 20 Colors[Colors.Female] // -> “Female”
Anonymous type
You don’t have to define a type in order to use it as a constraint - just define an anonymous type (looks a lot like a JSON object) right there inline in place of where you’d use a type name:
// Accepts any object that has a “length” property that’s a number type function getLength(x: { length: number }): number { return x.length; }
## Classes
Basic Class
class Animal {
// Can have properties:
name: string;
birthYear: number;
// Even private/protected properties:
private _birthYear: number;
private _id: number;
// Can have getter/setter properties
// Note: they look like properties from the outside,
// which means that they even satisfy an interface!
get birthYear(): number {
return this._birthYear;
}
set birthYear(year: number) {
this._birthYear = year;
}
get id(): number {
return this._id;
}
// A constructor (aka the constructor function)
constructor(name: string) {
this.name = name;
this._id = Animal.generateId();
}
// Or methods:
getAge(): number {
return new Date().getFullYear() - this.birthYear;
}
// Even static methods and properties...
static _lastId = 0;
static generateId(): number {
return Animal._lastId += 1;
}
}
And, by the way, since it is common to accept a constructor parameter and immediately save the value to a property, there’s a shortcut for that:
class Animal {
// Automatically creates a string property called "name"
constructor(public name: string) {
}
}
// Example usage:
let animal = new Animal('Fido');
console.log(animal.name); // -> "Fido"
Inheritance
In order to inherit from another class, just use the extends keyword:
class Dog extends Animal {
speak(): string {
return 'Woof!';
}
}
Extended classes also inherit constructors from their base classes:
let animal = new Dog(‘Fido’); // Works!
Abstract base class
If you want to make sure that a class is only ever used as a base class, you can mark it as abstract:
abstract class Animal { // … }
You can also make methods abstract as well:
abstract class Animal { // …
abstract speak(): string;
}
Then you extend the base class and implement its abstract members just like you would if it were a regular class:
class Dog extends Animal {
speak(): string {
return 'Woof!';
}
}
### Implementing an interface
You can also implement an interface, which looks similar to inheriting from a class, only using the implements keyword instead of extends:
class Dog implements IAnimal { // … }
And there’s nothing stopping a class from both implementing an interface and extending another class:
class Dog extends Animal implements IAnimal { // … }
Plus, TypeScript is smart enough to know that if a base class implements an interface, so do all of the classes that inherit/extend from that base class:
abstract class Animal implements IAnimal { // … }
// Also implements IAnimal! class Dog extends Animal { // … }
let animal: IAnimal = new Dog(‘Fido’); // Works!
## Generics
Generic function
function clone<T>(value: T): T {
return JSON.parse(JSON.stringify(value));
}
Generic class
class KeyValuePair<T, U> { key: T; value: U; }
Generic constraints
You can constrain the types you can use as generic parameters by saying that it “extends” a type:
function totalLength<T extends { length: number }>(x: T, y: T) {
return x.length + y.length;
}
That’s an example with an anonymous type, but of course you can use an interface or class as well:
interface IHaveALength { length: number; }
function totalLength<T extends IHaveALength>(x: T, y: T) {
return x.length + y.length;
}
## Modules
Internal Modules (Namespaces)
Use the namespace keyword, which acts kind of like an IIFE:
namespace TodoApp.Model {
interface Todo {
id: number;
name: string;
state: TodoState;
}
enum TodoState {
New = 1,
Active,
Complete,
Deleted
}
var someVariable; // only accessible within this block of code
}
var todo: TodoApp.Model.Todo; // ERROR! Don’t know about that type!(?)
If you want to access members of a namespace (even in the same file), you need to export them:
export interface Todo {
id: number;
name: string;
state: TodoState;
}
export enum TodoState {
New = 1,
Active,
Complete,
Deleted
}
}
var todo: TodoApp.Model.Todo; // Ah, now it works!
You can define an alias so that you don’t need to keep referring to a namespace with its full-fledged name:
namespace TodoApp.App {
import Model = TodoApp.Model;
var todo: Model.Todo;
}
External Modules (exports)
They work the same as namespaces, except the file is the “namespace”.
Just be sure to set the compileSettings.module setting:
{
"compilerOptions": {
"target": "es5",
"module": "system",
}
}
Import with require
Say I’m in other file in the same folder as the Model.ts file I’ve been showing.
I can refer to items in that Model.ts file with this syntax:
import Model = require('./Model');
I can even establish an alias for items in that file like this:
import Model = require('./Model');
import Todo = Model.Todo;
Import with ECMAScript import keyword
Say I’m in other file in the same folder as the Model.ts file I’ve been showing.
I can refer to items in that Model.ts file with this syntax:
import * as Model from './Model';
I can even establish an alias for items in that file by importing them explictly like this:
import { Todo } from './model';
## Decorators
Since decorators are an experimental feature, you first have to enable them in your configuration:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
Class Decorator
First, create your decorator function, which is just a plain old function that accepts the target parameter:
function validatable(target: Function) { target.prototype.validate = function() { console.log(‘TODO: implement validation!’); }; }
Then, apply it to your class:
@validatable class Request { }
In this example, the validatable decorator dynamically adds a new method called validate to every Request object, which means that I can do this:
let request = new Request(); request.validate(); // -> “TODO: implement validation!”
Method Decorator
Method decorators are the same basic principle as Class Decorators, only with a slightly more complex signature that looks like this:
Here’s a working example of a “log” decorator that wraps a method and logs its parameters and return values:
function log(
target: Object, methodName: string,
descriptor: TypedPropertyDescriptor<Function>
) {
var originalMethod = descriptor.value;
descriptor.value = function (...args) {
console.log(`${methodName}(${JSON.stringify(args)})`)
let returnValue = originalMethod.apply(this, args);
console.log(`${methodName}(${JSON.stringify(args)}) => ${JSON.stringify(\
returnValue)}`)
return returnValue;
}
}
Then just apply it like you apply a class decorator:
@log function getLength(x: { length: number}) { return x.length; }
Property Decorator
A property decorator is just a function with this signature:
function name(target: Object, propertyKey: string | symbol) => void;
target is the instance of the object being created.
For example:
function required(target: Object, propertyName: string) {
console.log(`${propertyName} is required`);
}
class SomeClass {
@required
importantProperty;
}
new SomeClass(); // -> “importantProperty is required”
Decorator Factory
A decorator factory is a function that returns a decorator function, like this:
function regex(pattern: string) {
return function (target: Object, propertyName: string) {
console.log(`${propertyName} must match regex ${pattern}`);
}
}
class SomeClass {
@regex('[a-zA-Z0-9]')
importantProperty;
}
new SomeClass(); // -> “importantProperty must match regex [a-zA-Z0-9]