Working with React and TypeScript

An introduction to the development of React applications with Atom and TypeScript

We are about to develop the famous TODO App from the TodoMVC project using React and TypeScript:

Screen Shot 2015-10-12 at 12.20.29 a.m..png

In this post you will learn about the following:

Let’s get started!

 1. Setting up the environment

We will start by setting up the environment:

$ npm install -g typescript tsd

Note: use sudo if you are using OSX

Screen Shot 2015-10-11 at 11.58.23 p.m..png

$ apm install atom-typescript

This plugin has some cool features like HTML to TSX:

68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f547970655374726f6e672f61746f6d2d747970657363726970742d6578616d706c65732f6d61737465722f73637265656e732f68746d6c746f7473782e676966.gif

Or dependency view:

68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f547970655374726f6e672f61746f6d2d747970657363726970742d6578616d706c65732f6d61737465722f73637265656e732f646570656e64656e6379566965772f7465617365722e706e67.png

Please visit the project’s page on GitHub to learn more about the atom-typescript features.

This extension helps us to debug React applications by displaying the value of the properties and state of a selected component.

Screen Shot 2015-10-12 at 9.50.35 p.m..png

 2. Setting up the project

By the end of this tutorial the project structure will be similar to the following one:

├── index.html
├── js
│   ├── app.js
│   ├── app.tsx
│   ├── constants.js
│   ├── constants.ts
│   ├── footer.js
│   ├── footer.tsx
│   ├── interfaces.d.ts
│   ├── todoItem.js
│   ├── todoItem.tsx
│   ├── todoModel.js
│   ├── todoModel.ts
│   ├── tsconfig.json
│   ├── utils.js
│   └── utils.ts
├── node_modules
│   ├── director
│   ├── react
│   └── todomvc-app-css
├── package.json
├── tsd.json
└── typings
    ├── react
    │   ├── react-global.d.ts
    │   └── react.d.ts
    └── tsd.d.ts

Let’s start by creating the application’s root folder.

$ mkdir typescript-react
$ cd typescript-react

Then create new package.json file inside the application’s root folder:

{
private: true,
  dependencies: {
    director: "^1.2.0",
    react: "^0.13.3",
    todomvc-app-css: "^2.0.0"
  }
}

You can then install the project dependencies using npm:

# from the application's root folder
$ npm install

This command should create a folder named node_modules inside the application’s root folder. The node_modules should contain 3 folders named: director, react and todomvc-app-css.

├── node_modules
│   ├── director
│   ├── react
│   └── todomvc-app-css

We will now install some TypeScript type definitions files.

Type definitions files are used to declare the interfaces of the public API of third party libraries like React. These interfaces can be used by the IDEs to help us during the development of TypeScript applications with features like IntelliSense.

The type definitions files are also used by the TypeScript compiler to ensure that we are using the third party libraries correctly.

We are going to need the React type definitions. We can install them using the following command:

# from the application's root folder
$ tsd init
$ tsd install react --save

The command above will create a file named tsd.json and a folder named typings in the application’s root folder. The typings folder should contain a folder named react.

We also need to manually download and save a file named react-global.d.ts under the typings/react folder.

└── typings
    ├── react
    │   ├── react-global.d.ts
    │   └── react.d.ts
    └── tsd.d.ts

Now, let’s create the index.html file inside the application’s root folder:

<!doctype html>
<html lang="en" data-framework="typescript">
  <head>
    <meta charset="utf-8">
    <title>React • TodoMVC</title>
    <link rel="stylesheet" 
          href="node_modules/todomvc-common/base.css">

    <link rel="stylesheet" 
          href="node_modules/todomvc-app-css/index.css">

  </head>
  <body>
    <section class="todoapp"></section>
    <footer class="info">
      <p>Double-click to edit a todo</p>
      <p>
        Created by 
        <a href="http://github.com/remojansen/">Remo H. Jansen</a>
      </p>
      <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
    </footer>

    <script type="text/javascript" 
            src="node_modules/react/dist/react-with-addons.js">
    </script>

    <script type="text/javascript" 
            src="node_modules/director/build/director.js">
    </script>

    <script type="text/javascript" src="js/constants.js"></script>
    <script type="text/javascript" src="js/utils.js"></script>
    <script type="text/javascript" src="js/todoModel.js"></script>
    <script type="text/javascript" src="js/todoItem.js"></script>
    <script type="text/javascript" src="js/footer.js"></script>
    <script type="text/javascript" src="js/app.js"></script>
  </body>
</html>

At this point you should have the following files and folders in place:

├── index.html
├── node_modules
│   ├── director
│   ├── react
│   └── todomvc-app-css
├── package.json
├── tsd.json
└── typings
    ├── react
    │   ├── react-global.d.ts
    │   └── react.d.ts
    └── tsd.d.ts

You may have noticed that some of the JavaScript files referenced by our index.html file are missing. We will now proceed to solve that problem.

 3. The basics about React components

Components are he main building block of a React application. A component represents a self-contained piece of UI. A component will usually display some data and be able handle some kind of user interaction.

A component can contain child components. The application that we are about to develop is really small, so we will only develop one top-level component named TodoApp.

The TodoApp component will be composed of multiple components, including one TodoFooter component and a list of TodoItem components.

7yjwysnxnekg_retina.png

Components differentiate two different sets of data: properties and state.

 Properties

Props (short for properties) are a Component’s configuration, its options if you may. They are received from above and immutable as far as the Component receiving them is concerned.

A Component cannot change its props, but it is responsible for putting together the props of its child Components.

 State

The state starts with a default value when a Component mounts and then suffers from mutations in time (mostly generated from user events). It’s a serialisable representation of one point in time—a snapshot.

A Component manages its own state internally, but—besides setting an initial state—has no business fiddling with the state of its children. You could say the state is private.

When we declare a new React component using TypeScript we must declare the interface of its properties and state as follows:

class SomeComponent extends React.Component<ISomeComponentProps, ISomeComponentState> {
  // ...
}

Now that we have our project structure in place and we know the basics about components it is time to start developing our components.

 4. Developing React components with TypeScript

Let’s create a new folder named js under the application’s root folder.

We are going to create the following files:

├── js
│   ├──interfaces.d.ts
│   ├── constants.ts
│   ├── utils.ts
│   ├── todoModel.js
│   ├── footer.tsx
│   ├── todoItem.tsx
│   └── app.tsx

Feel free to create them now or do it as we implement each one of them.

 interfaces.d.ts

We will use this file to define all the interfaces in our application. We use the extension .d.ts (which is also used by the type definition files) instead of .ts because this file will not be transpiled into a JavaScript file. The file is not transpiled because TypeScript interfaces are not transformed into JavaScript code during the compilation process.

// Defines the interface of the structure of a task
interface ITodo {
  id: string,
  title: string,
  completed: boolean
}

// Defines the interface of the properties of the TodoItem component
interface ITodoItemProps {
  key : string,
  todo : ITodo;
  editing? : boolean;
  onSave: (val: any) => void;
  onDestroy: () => void;
  onEdit: ()  => void;
  onCancel: (event : any) => void;
  onToggle: () => void;
}

// Defines the interface of the state of the TodoItem component
interface ITodoItemState {
  editText : string
}

// Defines the interface of the properties of the Footer component
interface ITodoFooterProps {
  completedCount : number;
  onClearCompleted : any;
  nowShowing : string;
  count : number;
}

// Defines the TodoModel interface
interface ITodoModel {
  key : any;
  todos : Array<ITodo>;
  onChanges : Array<any>;
  subscribe(onChange);
  inform();
  addTodo(title : string);
  toggleAll(checked);
  toggle(todoToToggle);
  destroy(todo);
  save(todoToSave, text);
  clearCompleted();
}

// Defines the interface of the properties of the App component
interface IAppProps {
  model : ITodoModel;
}

// Defines the interface of the state of the App component
interface IAppState {
  editing? : string;
  nowShowing? : string
}

 constants.ts

This file is used to expose some constants. The constants are used to store the numeric value of the keyboard keys (ENTER_KEY and ESCAPE_KEY) that we will use later to set some events listeners.

We will also use some values to identify the currently displayed list of tasks by its status:

namespace app.constants {
  export var ALL_TODOS = 'all';
  export var ACTIVE_TODOS = 'active';
  export var COMPLETED_TODOS = 'completed';
  export var ENTER_KEY = 13;
  export var ESCAPE_KEY = 27;
}

 utils.ts

This file contains a class named Utils. The Utils class is no more that a collection of utility static functions.

namespace app.miscelanious {

  export class Utils {

    // generates a new Universally unique identify (UUID) 
    // the UUID is used to identify each of the tasks
    public static uuid() : string {
      /*jshint bitwise:false */
      var i, random;
      var uuid = '';

      for (i = 0; i < 32; i++) {
        random = Math.random() * 16 | 0;
        if (i === 8 || i === 12 || i === 16 || i === 20) {
          uuid += '-';
        }
        uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random))
          .toString(16);
      }

      return uuid;
    }

    // adds 's' to the end of a given world when count > 1
    public static pluralize(count, word) {
      return count === 1 ? word : word + 's';
    }

    // stores data using the localStorage API
    public static store(namespace, data?) {
      if (data) {
        return localStorage.setItem(namespace, JSON.stringify(data));
      }

      var store = localStorage.getItem(namespace);
      return (store && JSON.parse(store)) || [];
    }

    // just a helper for inheritance
    public static extend(...objs : any[]) : any {
      var newObj = {};
      for (var i = 0; i < objs.length; i++) {
        var obj = objs[i];
        for (var key in obj) {
          if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
          }
        }
      }
      return newObj;
    }

  }
}

 todoModel.ts

TodoModel is a generic “model” object. Since this application is really small it may not even be worth separating this logic out, but we do this to demonstrate one way to separate out parts of your application.

/// <reference path="../typings/react/react-global.d.ts" />
/// <reference path="./interfaces.d.ts"/>

namespace app.models {

  export class TodoModel implements ITodoModel {

    public key : string;            // key used for local storage
    public todos : Array<ITodo>;    // a list of tasks
    public onChanges : Array<any>;  // a list of events

    constructor(key) {
      this.key = key;
      this.todos = app.miscelanious.Utils.store(key);
      this.onChanges = [];
    }

    // the following are some methods 
    // used to manipulate the list of tasks

    public subscribe(onChange) {
      this.onChanges.push(onChange);
    }

    public inform() {
      app.miscelanious.Utils.store(this.key, this.todos);
      this.onChanges.forEach(function (cb) { cb(); });
    }

    public addTodo(title : string) {
      this.todos = this.todos.concat({
        id: app.miscelanious.Utils.uuid(),
        title: title,
        completed: false
      });

      this.inform();
    }

    public toggleAll(checked) {
      // Note: it's usually better to use immutable 
      // data structures since they're easier to 
      // reason about and React works very 
      // well with them. That's why we use 
      // map() and filter() everywhere instead of 
      // mutating the array or todo items themselves.
      this.todos = this.todos.map<ITodo>((todo : ITodo) => {
        return app.miscelanious.Utils.extend(
          {}, todo, {completed: checked}
        );
      });

      this.inform();
    }

    public toggle(todoToToggle) {
      this.todos = this.todos.map<ITodo>((todo : ITodo) => {
        return todo !== todoToToggle ?
          todo :
          app.miscelanious.Utils.extend(
            {}, todo, {completed: !todo.completed}
          );
      });

      this.inform();
    }

    public destroy(todo) {
      this.todos = this.todos.filter(function (candidate) {
        return candidate !== todo;
      });

      this.inform();
    }

    public save(todoToSave, text) {
      this.todos = this.todos.map(function (todo) {
        return todo !== todoToSave ? todo : app.miscelanious.Utils.extend({}, todo, {title: text});
      });

      this.inform();
    }

    public clearCompleted() {
      this.todos = this.todos.filter(function (todo) {
        return !todo.completed;
      });

      this.inform();
    }
  }

}

 footer.tsx

This file uses the .tsx extension instead of the .ts extension because it contains some TSX code.

TSX is a typed superset of JSX. We will use TSX instead of HTML of client-side templates like Handlebars because TSX and JSX are used to generate an in-memory representation of the DOM. When the components state or properties change Reacts calculates the most efficient way to update the in-memory representation of the DOM and then proceeds to apply those changes to the real DOM. This process makes React highly efficient when it comes to DOM manipulation.

Note: We need use some extra compiler options to compile .tsx. We will learn more about this topic towards the end of this post.

The footer component allows users to filter the lists of tasks by their status and displays the count of tasks. This component has no state (Note how {} is passed to React.Component as the interface of its state) but it has some properties (ITodoFooterProps) that are set by its parent component (the TodoApp component).

/// <reference path="../typings/react/react-global.d.ts" />
/// <reference path="./interfaces.d.ts"/>

namespace app.components {

  export class TodoFooter extends React.Component<ITodoFooterProps, {}> {

    public render() {
      var activeTodoWord = app.miscelanious.Utils.pluralize(this.props.count, 'item');
      var clearButton = null;

      if (this.props.completedCount > 0) {
        clearButton = (
          <button
            className="clear-completed"
            onClick={this.props.onClearCompleted}>
            Clear completed
          </button>
        );
      }

      // React idiom for shortcutting to `classSet` since it'll be used often
      var cx = React.addons.classSet;
      var nowShowing = this.props.nowShowing;
      return (
        <footer className="footer">
          <span className="todo-count">
            <strong>{this.props.count}</strong> {activeTodoWord} left
          </span>
          <ul className="filters">
            <li>
              <a
                href="#/"
                className={cx({selected: nowShowing === app.constants.ALL_TODOS})}>
                  All
              </a>
            </li>
            {' '}
            <li>
              <a
                href="#/active"
                className={cx({selected: nowShowing === app.constants.ACTIVE_TODOS})}>
                  Active
              </a>
            </li>
            {' '}
            <li>
              <a
                href="#/completed"
                className={cx({selected: nowShowing === app.constants.COMPLETED_TODOS})}>
                  Completed
              </a>
            </li>
          </ul>
          {clearButton}
        </footer>
      );
    }
  }

}

 todoItem.tsx

The TodoItem component represents one of the tasks in the list of tasks.

This component has both properties (ITodoItemProps) and state (ITodoItemState).

The component initial’s state is set in the component’s constructor by itself while the properties are passed as constructor arguments and are set by the component’s parent component (the TodoApp component).

/// <reference path="../typings/react/react-global.d.ts" />
/// <reference path="./interfaces.d.ts"/>

namespace app.components {

  export class TodoItem extends React.Component<ITodoItemProps, ITodoItemState> {

    constructor(props : ITodoItemProps){
      super(props);
      // set initial state
      this.state = { editText: this.props.todo.title };
    }

    public handleSubmit(event) {
      var val = this.state.editText.trim();
      if (val) {
        this.props.onSave(val);
        this.setState({editText: val});
      } else {
        this.props.onDestroy();
      }
    }

    public handleEdit() {
      this.props.onEdit();
      this.setState({editText: this.props.todo.title});
    }

    public handleKeyDown(event) {
      if (event.which === app.constants.ESCAPE_KEY) {
        this.setState({editText: this.props.todo.title});
        this.props.onCancel(event);
      } else if (event.which === app.constants.ENTER_KEY) {
        this.handleSubmit(event);
      }
    }

    public handleChange(event) {
      this.setState({editText: event.target.value});
    }

    // This is a completely optional performance enhancement 
    // that you can implement on any React component. If you 
    // were to delete this method the app would still work 
    // correctly (and still be very performant!), we just use it 
    // as an example of how little code it takes to get an order
    // of magnitude performance improvement.
    public shouldComponentUpdate(nextProps, nextState) {
      return (
        nextProps.todo !== this.props.todo ||
        nextProps.editing !== this.props.editing ||
        nextState.editText !== this.state.editText
      );
    }

    // Safely manipulate the DOM after updating the state 
    // when invoking this.props.onEdit() in the handleEdit
    // method above. 
    public componentDidUpdate(prevProps) {
      if (!prevProps.editing && this.props.editing) {
        var node = React.findDOMNode<HTMLInputElement>(this.refs["editField"]);
        node.focus();
        node.setSelectionRange(node.value.length, node.value.length);
      }
    }

    public render() {
      return (
        <li className={React.addons.classSet({
          completed: this.props.todo.completed,
          editing: this.props.editing
        })}>
          <div className="view">
            <input
              className="toggle"
              type="checkbox"
              checked={this.props.todo.completed}
              onChange={this.props.onToggle}
            />
            <label onDoubleClick={ e => this.handleEdit() }>
              {this.props.todo.title}
            </label>
            <button className="destroy" onClick={this.props.onDestroy} />
          </div>
          <input
            ref="editField"
            className="edit"
            value={this.state.editText}
            onBlur={ e => this.handleSubmit(e) }
            onChange={ e => this.handleChange(e) }
            onKeyDown={ e => this.handleKeyDown(e) }
          />
        </li>
      );
    }
  }

}

 app.tsx

This file contains the application’s entry point and the declaration of the TodoApp component which is the only top-level component in this application.

/// <reference path="../typings/react/react-global.d.ts" />
/// <reference path="./interfaces.d.ts"/>

// We should have installed a type declaration file but
// for the director npm package but it is not available
// so we will use this declaration to avoid compilation 
// errors for now.
declare var Router : any;

var TodoModel = app.models.TodoModel;
var TodoFooter = app.components.TodoFooter;
var TodoItem = app.components.TodoItem;

namespace app.components {

  export class TodoApp extends React.Component<IAppProps, IAppState> {

    constructor(props : IAppProps) {
      super(props);
      this.state = {
        nowShowing: app.constants.ALL_TODOS,
        editing: null
      };
    }

    public componentDidMount() {
      var setState = this.setState;
      // we will configure the Router here
      // our router is provided by the
      // director npm module
      // the router observes changes in the URL and 
      // triggers some component's event accordingly 
      var router = Router({
        '/': setState.bind(this, {nowShowing: app.constants.ALL_TODOS}),
        '/active': setState.bind(this, {nowShowing: app.constants.ACTIVE_TODOS}),
        '/completed': setState.bind(this, {nowShowing: app.constants.COMPLETED_TODOS})
      });
      router.init('/');
    }

    public handleNewTodoKeyDown(event) {
      if (event.keyCode !== app.constants.ENTER_KEY) {
        return;
      }

      event.preventDefault();

      var val = React.findDOMNode<HTMLInputElement>(this.refs["newField"]).value.trim();

      if (val) {
        this.props.model.addTodo(val);
        React.findDOMNode<HTMLInputElement>(this.refs["newField"]).value = '';
      }
    }

    public toggleAll(event) {
      var checked = event.target.checked;
      this.props.model.toggleAll(checked);
    }

    public toggle(todoToToggle) {
      this.props.model.toggle(todoToToggle);
    }

    public destroy(todo) {
      this.props.model.destroy(todo);
    }

    public edit(todo) {
      this.setState({editing: todo.id});
    }

    public save(todoToSave, text) {
      this.props.model.save(todoToSave, text);
      this.setState({editing: null});
    }

    public cancel() {
      this.setState({editing: null});
    }

    public clearCompleted() {
      this.props.model.clearCompleted();
    }

    // the JSX syntax is quite intuitive but check out
    // https://facebook.github.io/react/docs/jsx-in-depth.html
    // if you need additional help
    public render() {
      var footer;
      var main;
      var todos = this.props.model.todos;

      var shownTodos = todos.filter(function (todo) {
        switch (this.state.nowShowing) {
        case app.constants.ACTIVE_TODOS:
          return !todo.completed;
        case app.constants.COMPLETED_TODOS:
          return todo.completed;
        default:
          return true;
        }
      }, this);

      var todoItems = shownTodos.map(function (todo) {
        return (
          <TodoItem
            key={todo.id}
            todo={todo}
            onToggle={this.toggle.bind(this, todo)}
            onDestroy={this.destroy.bind(this, todo)}
            onEdit={this.edit.bind(this, todo)}
            editing={this.state.editing === todo.id}
            onSave={this.save.bind(this, todo)}
            onCancel={ e => this.cancel() }
          />
        );
      }, this);

      var activeTodoCount = todos.reduce(function (accum, todo) {
        return todo.completed ? accum : accum + 1;
      }, 0);

      var completedCount = todos.length - activeTodoCount;

      if (activeTodoCount || completedCount) {
        footer =
          <TodoFooter
            count={activeTodoCount}
            completedCount={completedCount}
            nowShowing={this.state.nowShowing}
            onClearCompleted={ e=> this.clearCompleted() }
          />;
      }

      if (todos.length) {
        main = (
          <section className="main">
            <input
              className="toggle-all"
              type="checkbox"
              onChange={ e => this.toggleAll(e) }
              checked={activeTodoCount === 0}
            />
            <ul className="todo-list">
              {todoItems}
            </ul>
          </section>
        );
      }

      return (
        <div>
          <header className="header">
            <h1>todos</h1>
            <input
              ref="newField"
              className="new-todo"
              placeholder="What needs to be done?"
              onKeyDown={ e => this.handleNewTodoKeyDown(e) }
              autoFocus={true}
            />
          </header>
          {main}
          {footer}
        </div>
      );
    }
  }
}

var model = new TodoModel('react-todos');
var TodoApp = app.components.TodoApp;

function render() {
  React.render(
    <TodoApp model={model}/>,
    document.getElementsByClassName('todoapp')[0]
  );
}

model.subscribe(render);
render();

Make sure that the this operator is pointing to the right element at all times. For example, you should use arrow functions:

onKeyDown={ e => this.handleNewTodoKeyDown(e) }

instead of

onKeyDown={ this.handleNewTodoKeyDown }

To ensure that the this operator is pointing to the component inside the handleNewTodoKeyDown function.

 5. Compiling the application

To compile our application we must add a file named tsconfig.json under the js folder:

{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "moduleResolution": "node",
        "isolatedModules": false,
        "jsx": "react",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "declaration": false,
        "noImplicitAny": false,
        "removeComments": true,
        "noLib": false,
        "preserveConstEnums": true,
        "suppressImplicitAnyIndexErrors": true
    },
    "filesGlob": [
        "**/*.ts",
        "**/*.tsx",
        "!node_modules/**"
    ],
    "files": [
        "constants.ts",
        "interfaces.d.ts",
        "todoModel.ts",
        "utils.ts",
        "app.tsx",
        "footer.tsx",
        "todoItem.tsx"
    ],
    "exclude": []
}

If we check out the TypeScript compiler options we can find out how to use the tsconfig.json file:

The --project or -p can be used to compile the project in the given directory. The directory needs to contain a tsconfig.json file to direct compilation.

We can compile our application using the following command:

# from the application's root folder
$ tsc -p js

This should create the following JavaScript files under the js folder:

├── js
│   ├── app.js
│   ├── constants.js
│   ├── footer.js
│   ├── todoItem.js
│   ├── todoModel.js
│   └── utils.ts

These are the files that were referenced in our index.html file:

<script type="text/javascript" src="js/constants.js"></script>
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/todoModel.js"></script>
<script type="text/javascript" src="js/todoItem.js"></script>
<script type="text/javascript" src="js/footer.js"></script>
<script type="text/javascript" src="js/app.js"></script>

We are now ready to run our application.

 6. Running the application

To run the application, we need a web server. We will use the npm module http-server.

We can install this package using the following commmand:

$ npm install -g http-server

Note: use sudo if you are using OSX

Use the following command to run the application:

# from the application's root folder
$ http-server

If you open a browser and navigate to http://127.0.0.1:8080/ you should be able to see the application running:

Screen Shot 2015-10-12 at 12.20.29 a.m..png

Remember to open the chrome developer tools to take a look to the React developer tools extension for chrome and how the value of the properties and state of the components change as you interact with the application.

Screen Shot 2015-10-12 at 9.50.35 p.m..png

 7. Conclusions

In this post we have learned how to set up a development environment and create a new project to work with TypeScript and React.

You can browse the source code online at GitHub.

Are you thirsty for more? If so, check out Typed React and Redux by Jack Hsu.

We will keep this blog updated and write more about React and TypeScript in the future. Don’t forget to subscribe if you don’t want to miss it out!

Please feel free to talk about this article with us via @OweR_ReLoaDeD and @WolkSoftwareLtd.

 
1,997
Kudos
 
1,997
Kudos

Now read this

Decorators & metadata reflection in TypeScript: From Novice to Expert (Part III)

An in-depth look to the TypeScript implementation of decorators and how they make possible new exciting JavaScript features like reflection or dependency injection. This article is the third part of a series: PART I: Method decorators... Continue →