The application is very simple, as our focus is on looking at how TypeScript helps build better web applications. With this application, we intend to show you how to create an application with TypeScript, showcase the basic features of TypeScript, show you how to debug TypeScript code in the browser, and give you an overview of the JavaScript code that's generated by TypeScript.
The application has no dependencies on any JavaScript library and is purely written in TypeScript. In this application, we have written all our code in a single file (todo.ts
) just to keep it concise, although in real-world applications, we would want to create separate files to reduce code complexity and have separation of concerns.
The todo.ts
file contains a couple of classes—todo
and todolist
—and has an interface ITodo
.
We have a Todo
class, which has three properties: name, description, and completed. The Todo
class implements an interface ITodo
:
interface ITodo{
name:string;
description: string;
completed: boolean;
}
Implementing an interface helps maintain code consistency; in large applications, if any class implements an interface, that would act as a contract between the class which implements the interface and the class/module which creates an object of this class. An interface provides code abstraction and helps us create more manageable code. As we discussed earlier, an interface is just a TypeScript concept and, upon compilation, no JavaScript code is generated.
TypeScript provides a couple of ways to declare and assign values to member variables of a class:
- We can create three variables and then assign values to them inside the constructor function as follows:
class Todo implements ITodo{
public name:string;
public description: string;
public completed: boolean;
constructor(name:string, description:string, completed:boolean){
this.name = name;
this.description = description;
this.completed = completed;
}
}
Here we have three public variables in a class which is initialized inside the constructor.
- Another way, which is more concise, is seen in the following code snippet. Here, in the constructor itself, we declare the variables and TypeScript makes sure that they get initialized as well when the object of the class is created:
class Todo implements ITodo{
constructor(public name: string,
public description: string,
public completed: boolean){}
}
We have another class, TodoList
, which contains all the logic for our application. We have a static property of the type Todo Array
which will persist all the Todo
elements. It has two functions, createTodoItem
and allTodoItems
, which are responsible for creating a new todo
task and returning all the todo
tasks respectively:
class TodoList{
public static allTodos: Todo[]= new Array;
createTodoItem(name:string,description:string):number {
let newItem = new Todo(name,description, false);
let totalCount: number = TodoList.allTodos.push(newItem);
return totalCount;
}
allTodoItems():Todo[]{
return TodoList.allTodos;
}
}
We will go deeper into classes of TypeScript in later chapters; here, we will just look at the basic syntax and how functions are defined inside the class.
Declaring a function is straightforward, with the function name, set of parameters, and then, inside curly braces, the implementation. After the set of parameters is declared, we can also annotate the function with the data type of the return value from the function. By default, all properties and functions inside the class are public.
Before we look at the remaining functions in our class, let's look at the JavaScript generated so far for our code. If you are following along then, to generate the JavaScript file, use Ctrl + Shift + B on Windows and Cmd + Shift + B on macOS. This command will build the code and generate a corresponding JavaScript file. If we have the sourcemap
flag turned on in our taskconfig.json
then the build will generate another file with the name filename.js.map
, which in our case is todo.js.map
.
As we discussed earlier, for an interface, no code is generated, hence we just see the code for our two classes in the JavaScript file:
var Todo = (function () {
function Todo(name, description, completed) {
this.name = name;
this.description = description;
this.completed = completed;
}
return Todo;
}());
var TodoList = (function () {
function TodoList() {
}
TodoList.prototype.createTodoItem = function (
name, description) {
var newItem = new Todo(name, description, false);
var totalCount = TodoList.allTodos.push(newItem);
return totalCount;
};
TodoList.prototype.allTodoItems = function () {
return TodoList.allTodos;
};
return TodoList;
}());
TodoList.allTodos = new Array;
You will see that for both of our classes, the TypeScript compiler generated an immediately invoked function expression (IIFE) and assigned it to the variables Todo
and TodoList
respectively. An IIFE is a JavaScript function which is auto-executed when the JavaScript file is parsed. To identify the IIFE, look at the parentheses at the end of the function. The methods createTodoItem
and allTodoItems
are converted to prototype functions in JavaScript. The prototype is a JavaScript function which allows us to add behavior to our objects. In this case, the createTodoItem
and allTodoItems
functions introduce new behavior to the TodoList
object. We had an allTodos
static array in our TodoList
class, which in JavaScript is just an array.
The next function in our file is a window.onload
function which is called when the browser is loaded. The window.onload
function is a standard JavaScript function and here is a good example of TypeScript being a superset of JavaScript. All JavaScript functions can be used directly in TypeScript. In the window.onload
function, we just attach the event listener to the add button:
window.onload = function(){
let name= <HTMLInputElement>document.getElementById("todoName");
let description = <HTMLInputElement>document.getElementById(
"todoDescription");
document.getElementById("add").addEventListener(
'click',()=>toAlltask(name.value, description.value));
}
TypeScript allows us to type cast one type of value to another using <>
or the as
keyword. We will discuss this in more detail in later chapters; here, we should just know that document.getElementById
returns a type of HTMLElement
and, because we know that this element is a input element, we type cast that into HTMLInputElement
. The last function, todoAllTask
, is called every time we click and add a function, which takes a name and description entered by the user as input, calls the createTodoItem
function of the TodoList
class and then fetches the updated list of all Todo
items and assigns that to a div
using an ordered list. These two functions, when looked at in a JavaScript file, will be almost the same as what we wrote in TypeScript.
Debugging TypeScript code
Browsers don't understand TypeScript; they can only execute JavaScript code. That's why in our index.html
file, we have a reference to todo.js
and not to todo.ts
. Our application is very small in size and it would not be difficult to debug the JavaScript code in the browser, but real-world applications are often large-scale with many files, each with hundreds of lines of code, and it would not be easy to debug the JavaScript generated by the TypeScript compiler. TypeScript provides a workaround to help developers debug their TypeScript code directly in the browser by using a special file with a .map
extension. These are called source map files and we need to enable a flag in our tsconfig.json
to allow the compiler to generate this file. The source map allows a relationship to be created between a JavaScript file and its corresponding TypeScript file. Once we generate the source map file, we can see the TypeScript file in our browser's source tab along with the JavaScript file.
In our code, we have already set the source map flag to true
and you will see that the map file is generated when the compiler creates the JavaScript file. In the browser, press F12 on Windows or Cmd + Opt + I on macOS to open the developer tools. Then navigate to sources and you should see index.html
, site.css
, and an app
folder. Inside the app
folder, you should have both todo.js
and todo.ts
files. The following screenshot shows a snapshot of the developer tools in Chrome. On the left side is the list of files that were downloaded to the browser:
We can open the todo.ts
file and add breakpoints; when the code is executed by the browser, our breakpoints will be hit and we will be able to debug the code therein. This makes it very easy for developers to debug the code that they have written rather than the code generated by the compiler.