v19 launch sale ends in ... Get 25% off BUY NOW
3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 1

TypeScript and ES6 Concepts

STANDARD

TypeScript and ES6 Concepts

As I have mentioned, this course has some assumed knowledge of basic programming skills with JavaScript. However, you don’t need to have super in-depth technical knowledge of JavaScript, or TypeScript, or what ES6 is before beginning. We are going to improve our skills as we make our way through the course.

If you do already have a strong understanding of these concepts then you are welcome to skip this lesson. However, it never hurts to review the basics, often you still do pick up a thing or two.

Before we get into the Angular side of things, we are going to use this lesson to highlight some important concepts that apply to JavaScript generally that we will be making use of. We are going to briefly summarise concepts like:

  • What is ES6?
  • What is TypeScript?
  • What are types?
  • What are classes?
  • What are modules?
  • What are promises and async/await?
  • What are observables?

We are going to kick this off with a brief explanation of ES6 and TypeScript and then we will jump into some more details later.

ES6 refers to ECMAScript 6. This basically just means JavaScript, but at a technical level it would be more accurate to say that ECMAScript is the standard/spec, and JavaScript is the browser’s implementation of that standard. The reason I am referring to ECMAScript 6 specifically is that it introduced a lot of new/modern features to JavaScript that we will be making use of. Most notably, these features are:

  • The let/const keywords
  • Arrow functions/lexical scope
  • Classes
  • Promises

By this point in time, these features have been around for so long that they are no longer seen as “new” things to JavaScript — however, we will be heavily using them so it is worth having a deeper understanding of them.

We will be covering all of these things in this course, but you can also review the W3Schools documentation as well if you like.

TypeScript

TypeScript was created by Microsoft and in effect has essentially replaced JavaScript as a new language. However, TypeScript is a superset of JavaScript which basically means that it extends JavaScript. All valid JavaScript is valid TypeScript, but TypeScript also adds its own additional features into JavaScript.

If you’ve never heard of or used TypeScript — don’t worry — you don’t have to use all the TypeScript features and can just write standard JavaScript. We will be using TypeScript exclusively in this course, but we still often refer to it as just “JavaScript”.

Since TypeScript is a compiled language, it can contain all sorts of features that aren’t in the JavaScript/ECMAScript specification because when it is compiled it can be compiled down into valid JavaScript that will run in browsers.

The primary reason for using TypeScript, as the name suggests, is types. Types add type safety to our application, which adds an additional step where the TypeScript compiler can check we aren’t doing anything weird and warn us before we actually ship our code off to browsers.

For example, here is a standard JavaScript function:

function add(x, y) {
    return x + y;
}
add('a', 'b');

This is also valid TypeScript code — remember, all valid JavaScript is valid TypeScript. The intent of this function is to add two numbers together. However, we are calling it with two strings: a and b. This might cause some unexpected behaviour, but it is totally valid code and we won’t be warned about any potential problem here.

Here is a TypeScript version:

function add(x: number, y: number) {
    return x + y;
}
add('a', 'b'); // compiler error

Now we are explicitly enforcing that only numbers can be passed into this function. If we try to call it with a and b then the TypeScript compiler will complain about it and prevent our application from compiling successfully. If you are using something like Visual Studio Code then it will immediately highlight these errors right there in your editor.

TypeScript helps us even when we aren’t explicitly defining types ourselves. Often, TypeScript can infer what a type should be based on its usage. For example:

const value = 5;

TypeScript knows this is a number without us explicitly giving it a number type. So, even if you are just writing standard JavaScript code, TypeScript is still useful as it can infer types and warn you about things that might be a problem.

Not only that, but it also enables code completion with things like IntelliSense in VS Code. Let’s imagine we have an object like this:

const myObject = {
  name: 'Josh'
}

With TypeScript and IntelliSense, our editor will know the “shape” of this object. That means that if we are typing myObject. into our editor, all the potential properties will be known, and the editor can pop up a little window with suggestions for auto completion:

Auto code completion

Once you have used this you will wonder how you ever coded without it. Especially for third-party APIs that you might not be familiar with, IntelliSense and code completion is a massive boost to productivity and happiness.

Again, by this point in time this is really such a standard part of the web development experience that most new developers never would have written JavaScript without things like IntelliSense.

Sometimes it is useful to explicitly define types, even custom types, and sometimes it is not and we can just let TypeScript do its thing. As we progress through the course, you will see different ways in which we utilise TypeScript.

Classes

class Counter {

    count = 0;

    constructor () {
        console.log("The initial count is: ", this.count);
    }

    increment(x) {
        this.count += x;
        console.log("The count is now: ", this.count);
    }

    decrement(x) {
        this.count -= x;
        console.log("The count is now: ", this.count);
    }
}

This is a big one and something you would be familiar with if you have experience with more Object Oriented Programming (OOP) languages like Java and C++. People have been using class-like structures in JavaScript for a long time through the use of functions, but there has never been a way to create a “real” class until now. If you do not have any knowledge of classes and object-oriented programming, it might be useful to do a little bit of research on the topic.

However, we will be covering all the basics you need to know as we progress through this course, so a solid understanding of OOP and classes isn’t strictly mandatory. Let’s briefly talk about our Counter class example to give some context.

A class generally represents some kind of entity — in this case, a Counter. The class is a blueprint for creating an object or instance of that class. The class as we have defined it as above is useless until we do this:

const myCounter = new Counter();

When we do this, a new instance of our Counter will be created and assigned to myCounter. The constructor is run immediately, and so we will see this in the console:

The initial count is: 0

The count variable we have defined above the constructor is a member variable or class member. The state of this variable will be maintained throughout the entire life of our instance of the class. That means we can then call one of the methods we have defined on the class:

myCounter.increment(2);

and it will log out:

The count is now: 2

Since the count value is remembered, we can do the same thing again:

myCounter.increment(2);

and now the following will be logged out:

The count is now: 4

We can create multiple instances of our classes. If I were to do this:

const mySecondCounter = new Counter();

It will log out the following:

The initial count is: 0

This is an entirely new instance of the class, and it has its own count member variable that it will keep track of. There is much more to know about classes, but for our purposes, we mostly just need to know that classes can:

  • Have member variables that are accessible from anywhere within the class, and will maintain their state over the lifetime of the instance
  • Have methods that perform some functionality when invoked
  • Have a constructor method that runs when an object is instantiated using the class
  • Have multiple instances, and one instance of a class does not interfere with another instance of a class.

In Angular, we will be using classes a lot. Classes are used to power the logic for the templates/views in our application. Let’s continue with our Counter example, but now let’s consider it in the context of an Angular component.

Presumably, if we have a counter we are going to want to display it to the user in some way. Maybe we have a simple template like this:

<div>
  <p>The count is: 0</p>
</div>

But this is just a static template — it has no functionality. How do we get our Counter class to actually interact with this template and display the appropriate value?

Angular has a special way of doing this, our Counter class in an Angular context might look like this:

import { Component } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `
    <div>
      <p>The count is: 0</p>
    </div>
  `
})
export class CounterComponent {

  count = 0;

  constructor() {}

  increment(x) {
    this.count += x;
  }

  decrement(x) {
    this.count -= x;
  }

}

Angular uses the @Component decorator to associate a template with a class. In this course, we will be defining the templates inline directly inside of the @Component decorator as above, but it is also possible to link out to a separate template file by using the templateUrl property. We are going to talk about decorators and the @Component decorator in more detail later. For now, we just need to know that Angular is able to associate a particular class with a particular template.

We’re getting closer now, but our template is still just a static template, it doesn’t do anything but display 0. Now that we have associated the template with this class, we can access its methods and values. For example, to display the actual count value we would update our template to this:

<div>
  <p>The count is: {{count}}</p>
</div>

This is Angular specific syntax for an interpolation. It will evaluate whatever is inside the curly braces and display the value. In this case, we are accessing the count member variable from the class. We can also access the increment and decrement methods from our template, but we are going to discuss this in more detail in a later lesson.

One more thing I want to point out is that we never create a new instance of the class like we did with out initial example. We will talk more about this when we get to Dependency Injection, but briefly, you don’t actually need to create new instances of classes yourself in Angular. Angular handles creating new instances of classes for you.

Modules

//  lib/math.js
// Exports a function called "sum" that can be used elsewhere
export function sum (x, y) { return x + y }
// Exports a variable called "pi" that can be used elsewhere
export var pi = 3.141593 //

//  someApp.js
// Make everything that was exported in lib/math available in this file
import * as math from "lib/math"
// Use the "sum" method and "pi" variable that was imported
console.log("2PI = " + math.sum(math.pi, math.pi))

//  otherApp.js
// We can reuse those methods/variables in other files as well
import { sum, pi } from "lib/math"
console.log("2PI = " + sum(pi, pi))

ES6 modules allow you to modularise your code into packages that can be imported anywhere you need in your application. This is something that is going to be heavily used in Angular. We will get into this later, but essentially any components (classes) we create in our application we export so that we can import them elsewhere.

This is not to be confused with the concept of an Angular module or @NgModule. The concept is similar in that we are packaging up functionality into bundles to be used elsewhere, but the implementation and goals are a bit different. We will be talking about Angular modules in their own lesson.

Angular applications can use both standard ES6 modules and Angular modules — they address different concerns.

NOTE: Although we will discuss the concept of an @NgModule later, it is worth noting that standalone components are the newer concept here that can replace the @NgModule. The example applications we build will not use the @NgModule concept.

Promises

Promises are important for handling asynchronous behaviour. This can be a tricky concept to get initially. Not every action we perform in our code happens right away, or synchronously. If we want to add two numbers together or check a condition, these actions can be performed instantly. This means that we can perform the action, and then continue on with executing the rest of the code.

However, sometimes we may want to do things like retrieve data from a server, or perform some other action that could take anywhere from a few hundred milliseconds to 10 or more seconds to complete. If we were to perform these actions synchronously, that means we would need to wait for them to complete before our application could move on. The application would essentially be frozen whilst we wait for the operation to complete, which we definitely do not want.

Instead, we can perform a task asynchronously with promises. This allows the task to run in the background whilst the application continues operating as normal. Then when the operation completes, we can handle the response like this:

doSomething().then((response) => {
  console.log(response);
});

We use the then statement to handle the response the function returns. In this case, doSomething is a function that returns a promise. This means that when this function is triggered, the application won’t need to wait until the response is available before continuing. When response is available, we can do whatever we want with it then. This is why we supply a handler to then — we are defining what we want to happen when that promise has resolved.

It is important to understand the “time flow” of what is happening, and people often run into trouble with that. Let’s consider the following example:

this.name = 'Name not set yet';

getUsersName().then((response) => {
  this.name = response;
});

displayUsersName();

In this example, we are trying to retrieve the user’s name and then display it. Let’s assume that this is an asynchronous operation and that the name needs to be loaded from a server. The code above will not work.

This is perhaps the most common mistake I see, and it stems from not understanding how asynchronous code is executed. Here is what actually happens:

  1. Set this.name to Name not set yet
  2. Trigger the getUsersName promise that will fetch the users name
  3. Call the displayUsersName function to display the user’s name (Name not set yet) … waiting for promise to finish …
  4. Set this.name to the response from the promise

As you can see, the promise is triggered, but then the rest of the code executes without waiting for the promise to finish (as it should). The issue is that we are trying to display the user’s name before it is set, and the result will be “Name not set yet”. So, how do we handle this properly? If your code relies on the result of a promise, you need to make sure that you trigger that code as a result of the promise handler.

Here is the code that we should use:

this.name = 'Name not set yet';

getUsersName().then((response) => {
  this.name = response;
  displayUsersName();
});

and the flow for this code would look like this:

  1. Set this.name to Name not set yet
  2. Trigger the getUsersName promise that will fetch the user’s name … waiting for promise to finish …
  3. Set this.name to the response from the promise
  4. Call the displayUsersName function to display the user’s name

If this concept is new to you, it’s probably going to take a little getting used to, as it is kind of hard to wrap your brain around initially. If you run into issues with promises in your applications (like undefined variables that you expect to be defined), and you probably will, try to work out the “time flow” as I’ve described above. Remember that any .then handlers are going to trigger after the rest of your synchronous code has run. It is often also helpful to go into your code and add comments to indicate the order of execution.

Async/Await

The syntax we have just discussed for promises above is the “standard” syntax, but we can also use async and await to handle promises. I like using the standard promise syntax to explain the general concept, but using async and await is much easier in practice and it is what we will be using in this course.

The basic idea is that rather than creating a handler that is passed the result of the asynchronous operation like this:

doSomething().then((response) => {
  console.log(response);
});

We can do this instead:

const response = await doSomething();
console.log(response);

Any time we want to get the value from a resolved promise, we can just await it. It is important to know that the await keyword can only be used inside of an async method. That means that if we tried to do this:

myMethod() {
  const response = await doSomething();
  console.log(response);
}

…it would not work. However, if we mark myMethod as being async:

async myMethod() {
  const response = await doSomething();
  console.log(response);
}

…it will work. Any time we mark a method as async it will automatically wrap whatever that method returns in a promise (even if it does not return anything).

To further highlight the usefulness of async and await let’s go back to the example we discussed before:

this.name = 'Name not set yet';

getUsersName().then((response) => {
  this.name = response;
  displayUsersName();
});

I mentioned that it is important to trigger displayUsersName() inside of the promise handler, otherwise displayUsersName() would be called before this.name = response is called, due to the way asynchronous code works (promises get executed later, synchronous code gets executed right away). With async and await that example would look like this:

async myMethod() {
  this.name = 'Name not set yet';

  this.name = await getUsersName();
  displayUsersName();
}

This code above will work, as the await keyword will cause our code to wait until getUsersName() has resolved before it continues on to displayUsersName(). Not having to write handler functions already makes the code a lot nicer, but it is even more noticeable when we are waiting for several promises. For example, let’s say we have some code that first gets a users id, then it gets their name, and then it calls an order method. With the standard promise syntax that might look something like this:

myMethod(){
  this.getUserId().then((id) => {
    this.getUserName(id).then((name) => {
      this.createOrder(id, name).then((result) => {
        console.log(result);
      })
    })
  })
}

With async/await we could do the same thing like this:

async myMethod(){
  const id = await this.getUserId();
  const name = await this.getUserName(id);
  const result = await this.createOrder(id, name);
  console.log(result);
}

Observables

Although we will use promises in some cases for asynchronous code in this course, the vast majority of the time we will be using observables (and when we do end up using promises we will typically convert them to observables). This is a big topic and it is a key concept behind the philosophy of this course and what I think constitutes a modern/professional Angular application.

We will be dedicating an entire module to exploring observables, RxJS, and coding reactively/declaratively. But, in its most simple form, we can consider an observable to be a special type of promise — often promises and observables can be used interchangeably.

This is a gross over-simplification, but it is a good place to start.

The same example we just looked at above might look like this with an observable:

myMethod() {
  this.name = 'Name not set yet';

  getUsersName().subscribe((name) => {
    this.name = name;
    displayUsersName();
  })
}

In this case, we are assuming that getUsersName() returns an observable. To trigger an observable and handle the value it emits we subscribe to it and create a handler just like we did with the .then() syntax with promises.

One key difference with observables, although there are many, is that it doesn’t just have to resolve with one value and be done like a promise. An observable can stay active for a long time, and emit multiple values over time that will all be handled by our subscribe handler.

There is much, much, much more to observables. There is much to learn, but also so much power and productivity to gain. Just to highlight how important this concept is, it is probably going to make up at least 20% of our learning efforts throughout this entire course.

When I put in the effort to learn how to use observables properly, I think it was a moment in my career where I became a drastically better coder and started enjoying coding a whole lot more.

Block Scoping

Previously, if you defined a variable in JavaScript it was available anywhere within the function that it was defined in. The new block scoping features in ES6 allows you to use the let keyword to define a variable only within a single block of code like this:

for (let i = 0; i < a.length; i++) {
    let x = a[i];
}

If I were to try to access the x variable outside of the for loop, it would not be defined. In general, it is a good idea to use let as the default way to define a mutable (changeable) variable.

Generally, you should only ever have to use let and const to declare variables. You should use const by default, and if the value of the variable needs to be changed at some point, you can use let instead.

Arrow Functions & Lexical Scope

One of my favourite features in ES6 is arrow functions, which allow you to do something like this:

someFunction((response) => {
  console.log(response);
});

rather than:

someFunction(function(response){
  console.log(response);
});

At a glance, it might not seem all that great — just slightly less code to write. But what this allows you to do is maintain the parent’s scope. In the top example if I were to access the this keyword it would reference the parent’s scope, but in the bottom example I would need to do something like:

var me = this;

someFunction(function(response){
  console.log(me.someVariable);
});

…to achieve the same result. With the new syntax, there is no need to create a static reference to this you can just use this directly. Scope is another concept that can be a little tricky to understand initially (scope refers to the context in which the code is being executed, and what it currently has access to). The main benefit of using this syntax in our applications is that we will always be able to easily access the methods/properties of our components, in effect, it kind of allows you to forget about dealing with “scope”.

Recap

We’ve talked about a lot in this lesson, and if much of this is new to you it’s probably going to be hard to remember and understand everything. Don’t worry about it. We will continue to be exposed to these concepts as we progress through the course. If you find yourself getting stuck on any concepts you can always just come back to the relevant lesson or just do your own independent research and experiments. It always makes such a big difference in learning when you take the time to experiment with concepts yourself.

Which of the following best describes TypeScript...

What ES6 concept is used to help display/bind values and events to an Angular component's template?

Which best describes the role of a promise?