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:
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:
- Set
this.name
toName not set yet
- Trigger the
getUsersName
promise that will fetch the users name - Call the
displayUsersName
function to display the user’s name (Name not set yet) … waiting for promise to finish … - 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:
- Set
this.name
toName not set yet
- Trigger the
getUsersName
promise that will fetch the user’s name … waiting for promise to finish … - Set
this.name
to the response from the promise - 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...
TypeScript does not actually change anything about the existing JavaScript syntax, it just adds new features to it
Correct!
Incorrect
What ES6 concept is used to help display/bind values and events to an Angular component's template?
Correct!
Incorrect
Incorrect
Which best describes the role of a promise?
Incorrect
Incorrect
Correct!