Introduction to TypeScript
- Comprehensive course by Mash Hamadani teaching TypeScript from basics to advanced concepts.
- Focus on practical skills to build large-scale, maintainable applications.
- Requires basic JavaScript familiarity; no prior TypeScript knowledge necessary.
Setting Up Development Environment
- Install Node.js and TypeScript compiler globally using npm.
- Use Visual Studio Code for an optimal coding experience with TypeScript support.
- Compilation with
tscand configuring compiler options throughtsconfig.json.
Understanding TypeScript Basics
- TypeScript is a superset of JavaScript adding static typing.
- Static types catch errors at compile-time, improving code reliability.
- Supports all JavaScript features plus additional type annotations.
Core TypeScript Types and Features
- Primitives: number, string, boolean, null, undefined, object.
- Special types: any (should be avoided), unknown, never, void.
- Arrays with type annotations and tuples for fixed-length typed arrays.
- Enums for defining sets of named constants.
- Literal types and union types to constrain variable values.
- Nullable types and usage of optional chaining for safe property access.
Functions and Objects in TypeScript
- Functions require parameter and return type annotations for clarity.
- Optional and default parameters handling.
- Strict compiler options to detect unused variables and missing returns.
- Objects with typed properties, optional and readonly modifiers.
- Defining method signatures within objects.
Advanced Types in TypeScript
- Type aliases to create reusable custom types.
- Union and intersection types combining multiple types.
- Type narrowing techniques to refine variable types.
- Difference between any and unknown types and their appropriate use.
- Never type to represent unreachable code scenarios.
Object-Oriented Programming (OOP) in TypeScript
- Classes as blueprints for objects; constructors, properties, and methods.
- Access modifiers: public, private, protected.
- Parameter properties to simplify class constructors.
- Getters and setters for controlled property access.
- Index signatures for dynamic object properties.
- Static members for class-level properties and methods.
- Inheritance, method overriding, and polymorphism.
- Abstract classes and interfaces for defining contracts and reusability.
Generics
- Generic classes, functions, interfaces to create reusable type-safe code.
- Generic constraints to restrict types.
- Extending generic classes and type-safe utility functions.
- Keyof operator for property name validation.
- Type mapping for dynamic creation of modified types like readonly and optional.
Decorators
- Experimental feature enabling class, method, property, and parameter augmentation.
- Enabling decorators in
tsconfig.json. - Creating class decorators, decorator factories (parameterized decorators).
- Method decorators for enhancing or replacing method implementations.
- Accessor (get/set) decorators for property control.
- Property decorators for validation (e.g., minLength for passwords).
- Parameter decorators to collect metadata.
Modules and Code Organization
- Splitting code into files/modules for better maintainability.
- Exporting and importing classes, interfaces, and types.
- Module formats: CommonJS, ES6 Modules and configuring via compiler options.
- Default exports and named exports coexistence.
- Wildcard imports for importing all module members.
- Re-exporting for combining exports from multiple modules.
- Module resolution strategies in TypeScript.
Integrating JavaScript with TypeScript
- Including JavaScript files in TS projects and enabling
allowJs. - Enabling type checking in JS files with
checkJs. - Providing type info using JSDoc comments.
- Using declaration (
.d.ts) files for typing JS modules. - Incorporating third-party JS libraries using DefinitelyTyped declarations.
Building React Applications with TypeScript
- Creating React projects configured for TypeScript.
- Adding Bootstrap for basic styling.
- Defining functional components with typed props and state using interfaces.
- Using React hooks (
useState,useEffect) with proper type annotations. - Managing component state and props with strong typing.
- Implementing CRUD operations with typed services using Axios.
- Handling events with React's synthetic event types.
- Applying best practices for component design, prop typing, and state management.
You might find it helpful to explore Maîtrisez React : JSX, ReactDOM et création de composants for a deeper understanding of React fundamentals, which complements this section.
Final Notes
- Encouragement to use TypeScript for safer, more robust, and maintainable code.
- Recommended to follow best practices and compiler options for strict type checking.
- Suggested further learning through design patterns and advanced TypeScript topics.
For front-end design enhancement alongside React and TypeScript, the Building a Live Score Application with React and Tailwind CSS: Boot Camp Overview provides practical insights.
This course equips learners with practical, in-depth knowledge to confidently use TypeScript across various scenarios including vanilla JavaScript interop and React application development, highlighting error reduction, code clarity, and scalability.
Welcome to the ultimate TypeScript course. In this course, I'm going to teach you everything you need to know
about Typescript from the basics to more advanced concepts. So, by the end of this course, you'll be able to use
TypeScript to build large scale applications. If you're looking for a comprehensive, easy to follow, well,
practical course that takes you from zero to hero, this is the right TypeScript course for you. Everything
you need to know about TypeScript is in one place, so you don't need to jump back and forth between random tutorials.
I'm Mash Hamadani and I've taught millions of people how to advance their software engineering skills through my
YouTube channel and online school codewithm.com. If you're new here, be sure to subscribe as we upload new
videos all the time. Now, let's jump in and get started. [Music]
To take this course, you need to know Typescript. No, that was a joke. You don't need any familiarity with
TypeScript as I'm going to cover everything from scratch. But because Typescript is built on top of
JavaScript, you need basic familiarity with JavaScript concepts such as variables and constants, arrays,
objects, functions, arrow functions, destructuring, and so on. If you need to refresh the basics, I have a bunch of
tutorials and comprehensive courses you can take. I've put the links down below in case you are
interested. So, assuming that you're the right student for this course, now let's talk about how you should take this
course. First and foremost, I want you to watch this entire course all the way from the beginning to the end because
every lesson teaches you something new. If you have taken any of my other courses before, you know that I don't
waste your time with repetitive or useless stuff. So, make sure to watch every lesson. Now, while watching each
lesson, I want you to take notes. You can just write down some keywords on a piece of paper if you don't want to
write a lot of notes. I strongly believe that the act of writing things down will help you remember new things that you
learn. Now, when you get to the end of each section, make sure to do all the exercises. I've carefully designed these
exercises to help you better understand and remember the materials. Remember, the more you practice, the better you'll
be at TypeScript or coding in general. Welcome to the first section of the ultimate TypeScript course. We're
going to start this section with a quick introduction about TypeScript. We're going to talk about what it is, why we
use it, and when. Then we're going to set up our development environment and create our first TypeScript program.
Next, we'll talk about configuring the TypeScript compiler. And we'll finish this section by talking about debugging
TypeScript applications. So, let's jump in and get started. [Music]
Let's start by talking about the top three questions people ask about Typescript. What is Typescript? Why do
we need it? And how is it different from plain or vanilla JavaScript? Well, TypeScript is a programming language
created at Microsoft to address some of the shortcomings of JavaScript. You can think of it like the brother or sister
of JavaScript. JavaScript is like a kid without any discipline who does whatever he or she wants. Typescript on the other
hand is like a kid with some discipline. Now technically speaking, TypeScript is a programming language built on top of
JavaScript. So every JavaScript file is a valid TypeScript file. But TypeScript adds some really cool features to
JavaScript that help us build more robust and maintainable applications in less time. The most important feature
TypeScript offers is static typing. What does that mean? Well, we have two types of programming languages. Statically
typed and dynamically typed languages. In statically typed languages like C++, C and Java, we know the type of
variables at compile time or while coding. For example, we can declare a variable of type integer. And this
variable can only hold integer values, nothing else. So, we cannot set it to a string or another type of object. In
dynamically typed languages like JavaScript, Python and Ruby, the type of variables is dynamic. So it's determined
at runtime and it can also change. So we can declare a variable, set it to a number and then later on change it to a
string. So this variable does not have a fixed or a static type. The type is determined and may change at runtime.
Now this is great and gives us a lot of flexibility, but it can also lead to problems. What if we pass this variable
to a function that expects a number? Then our application might misbehave or crash. Now the problem is that we will
not know about these issues until we run our application or our unit tests. Well, assuming that we have them in place. So
we have to test every function with various edge cases to catch these bugs. And this is the problem that TypeScript
tries to solve. TypeScript is essentially JavaScript with type checking. With TypeScript, we explicitly
set the type of our variables upon declaration just like how we code in statically typeyped languages. Then we
pass our code to the TypeScript compiler and the compiler will tell us if we are doing something wrong. So we can catch a
lot of our mistakes at compile time. So if we declare a variable as a number, we cannot set it to a string. The
TypeScript compiler is going to stop us right there. And this happens at compile time. So we don't have to run our
application or our unit tests and test every piece of code to catch these errors. We can catch a lot of them
simply by compiling our application. Okay. But Typescript is more than just type checking. Most code editors these
days have great support for TypeScript. So they can detect the type of our variables and offer productivity
boosting features like code completion and refactoring. Also, TypeScript includes additional features that help
us write cleaner and more concise code. Now, over time, these features get added to JavaScript, but because we have
various browsers and runtime environments for executing JavaScript code, it takes some time until these
features are implemented in various browsers. So, by coding in TypeScript, we can use the features of future
JavaScript. So in a nutshell, TypeScript is built on top of JavaScript and we can use it wherever we use JavaScript on the
front end or the back end. So anything we can do with JavaScript, we can also do with TypeScript. Now all these great
benefits aside, let's talk about the drawbacks of TypeScript. First, with TypeScript, there is always a
compilation step involved because at this time browsers don't understand TypeScript code. So we have to give our
code to the TypeScript compiler to compile and translate into JavaScript. This process is called transpilation.
Second, with TypeScript, we have to be a bit more disciplined when writing code. So if you're a lazy programmer like our
famous old John Smith and want to get things done quickly, you may feel TypeScript is getting in the way. And
that's true. But if you're working on a large project with multiple developers, you would end up wasting more time
coding in vanilla JavaScript and catching those nasty bugs. So then you really want to use TypeScript. For
simple applications, you can totally get back to old vanilla JavaScript if that's what you prefer. All right, that's all
about TypeScript. Next, we're going to set up our development environment. [Music]
All right, the first thing we need is Node because we're going to use Node Package Manager or npm to install the
TypeScript compiler. I believe as a student of this course, you should have Node on your machine, but if not, head
over to nodejs.org and download the latest version over here. Once you do that, then open a terminal window and
run npm that is short for node package manager which we're going to use for installing third party packages in this
case typescript. Then we type I that is short for install -ash g for installing this globally so we can access the
typescript compiler in every folder and then we type the name of the package typescript. Now, if you're on Mac or
Linux and you get a permission error while running this command, you have to prefix it with sudo or sudo. Well, more
accurately, this is sudo because it's short for super user doom. But that aside, let's go ahead and install the
TypeScript compiler. Okay, great. Now, to verify our installation, we type TSC, that is
short for TypeScript compiler-V to get the version. So here I'm running Typescript version
4.6.3. You might install a newer version, but don't worry because everything I'm going to teach you here
will apply to newer TypeScript compilers. Now, in this course, just like my other courses, I'm going to use
Visual Studio Code or VS Code as my code editor. You're welcome to use any editor you prefer, but if you want to follow
along and use some of the shortcuts I'm going to teach you, I highly encourage you to use VS Code. In case you don't
have it, you can get it from code.visisual. at visualstudio.com. All right, so go ahead and set up your
development environment because in the next lesson, we're going to write our first TypeScript
[Music] program. All right. Now, I'm going to go to my desktop folder and create a new
folder for our project called Hello-World. You can call it anything you want and you can put it anywhere on
your machine. Now let's go into this folder and open it with VS Code. So we type code period. Now if this doesn't
work on your machine, you can simply drag and drop this folder onto VS Code. Okay. So here's our project. Now let's
add a new file here called index.ts. So every TypeScript file should have the TS extension. Now
earlier I told you that TypeScript is built on top of JavaScript. So we say TypeScript is a super of JavaScript
which means it has everything in JavaScript plus some additional features. So here we can write any
JavaScript code and that is valid TypeScript code. So we can write console.log and print hello
world. Now we can go back to our terminal window here or we can use the embedded terminal window in VS code. So
back in VS Code under the view menu look we have terminal. The shortcut on Mac is control and back tick. So we can open
the terminal window right here and using TypeScript compiler we can compile index.ts. Okay. Now let's open our
project folder. Look we have index.js which is the result of compilation. So we have the exact same
code here because in index.ts we haven't used any TypeScript features. So let's write a bit of TypeScript code. So here
in index.ts I'm going to declare a variable using the let keyword. I'm going to call it age and annotate it
with number. So by typing a colon followed by the type of variable we can annotate or explain a variable. Now we
can initialize it to let's say 20. Now, here's the beautiful part. Because we have declared age as a number, we cannot
set it to a string or another type of object. Look, we get this error right here. It's saying type string is not
assignable to type number. This is the beauty of using TypeScript. With TypeScript, we can catch a lot of our
mistakes at compile time. We don't have to run our application or our unit tests to find that we accidentally set a
number to a string. So let's remove this bad line and recompile our file. So back to the terminal tsc index.ts. Good. Now
take a look over here. Look at the JavaScript code that the TypeScript compiler generated. So
instead of the let keyword here we have var because by default the TypeScript compiler uses an older version of
JavaScript called ES5 which is short for ECMAScript 5. So emoscript is a standard or a specification while JavaScript is
an implementation of that specification. So ES5 is an old specification. It's been around for a long time and all
these features have been implemented in our browsers for a very very long time. I think more than a decade now. In the
next lesson, I'm going to show you how to configure the TypeScript compiler to target a newer JavaScript version. So
the code that will be generated would be more modern. So here's the interesting part. Instead of let we're using vir and
we don't have our type annotation. This is purely for the TypeScript compiler. The actual JavaScript code doesn't
specify the type of this variable. Okay. So that's it for now. Starting from the next section, we're going to explore
TypeScript features in detail. Next, I'm going to show you how to configure the TypeScript compiler.
[Music] All right, let's talk about creating a configuration file for the TypeScript
compiler. So here in the terminal, we run TSC double-enit. So this created a
configuration file called tsconfig.json with these settings. So let's close the terminal window and open
tsconfig right here. So in this file we have a number of settings and as you can see most of these are commented out by
default. We're only going to use a handful of them. So don't be intimidated by all these settings. You don't have to
learn all of them. In fact nobody knows all of them. I don't know all of them either. Now in case you're curious in
front of each setting you can see a description of what that setting is for. So let's talk about a few of them in
this lesson. The first one is target which specifies the version of JavaScript that the TypeScript compiler
is going to generate. So this is set to ES 2016 which is an old standard and it's been implemented in all browsers
out there. Now depending on where you want to deploy your application, you can use a higher target and that often
results in shorter and less concise code. So if you remove this value and press control and space, we can see all
valid values. So we have yes 2015, 16, 17, 18 and so on. Now in this lesson I'm going to leave this at yes 2016 because
this is the safest option for all browser applications out there. But again depending on where you want to
deploy your application and how much of older browsers you want to support, you can use a higher target. Now the next
setting we have here is module which is set to common.js. We'll talk about this setting later in the course where we
talk about modules. Now in this section in the module section we have a setting called root there which specifies the
directory that contains our source files. So let's remove the comment by pressing command and slash on Mac or
control and slash on Windows. So this is set to period slash which represents the current folder. Now by convention we
often put our source code into a separate folder. So, back to our project panel. Let's create a new folder here
called src and then move index.ts right here. Okay. Now, I'm going to delete index.js. We don't need it for now.
Okay. So, now we're going to change root there to period/source. Now, we have a similar
setting here under the emit section. That setting is called out. And this specifies the directory that will
contain our JavaScript files. So let's enable this and change it to dist. So when we compile our code using the
TypeScript compiler, our JavaScript files are going to be stored in dist or distributable folder. Okay. Now here we
have another useful setting called remove comments. So if we enable this, the TypeScript compiler is going to
remove all the comments that we add in our TypeScript code. So the generated JavaScript code is going to be shorter.
Okay. Now another useful setting in this section is no emit on error. So by default when we compile our code even if
you have errors in our code the TypeScript compiler will still generate JavaScript files. This is probably not
what we want. So the best option is to always enable this setting. So if you have any mistakes in our code, the
TypeScript compiler is not going to generate any JavaScript files. Okay. Now with this configuration file in place,
now we can go back to the terminal and compile our code simply by running tsc without any arguments. So we don't have
to type index.ts. We just run tsc and this will compile all TypeScript files in this
project. Take a look. All right. Now we have a new folder called disk that contains our JavaScript file. So this
was the basics of tsconfig. As we go through the course, we'll explore more of these useful
[Music] settings. All right. Now, let's see how we can debug our TypeScript applications
in VS Code. This is very useful when things go wrong and our code doesn't work as expected. So we can run our code
line by line and see what exactly happens under the hood. There are a few steps we need to follow. First we go to
tsconfig.json and here in the emit section we enable the source map feature. So a source map is a file that
specifies how each line of our TypeScript code maps to the generated JavaScript code. Let me show you. So
back to the terminal. Let's recompile our code. Good. Now look into this folder, we have a new file called
index.js.mmap. This is our source map. So if you look over here, you see some code that specifies how our TypeScript
code maps to our JavaScript code. Now this is not for us to understand. This is for debuggers. It's for machines.
Okay. So let's close this file. Now to make debugging more interesting, let's go to index.ts and write some logic. We
can say if age is less than 50, we're going to add 10 to it. Okay. Now we're going to click on the first line to
insert a break point. So when we start debugging, the execution stops right at this line at this break point. From this
point forward, we can execute our code line by line. Okay. Now we go to the debug panel and click on create a
launch.json file. Now from this dropdown we select Node.js and this creates a new file called
launch.json in our project. I'll show you that in a second. So this is a JSON file with some configuration that tells
VS Code how to debug this application. So we're going to use node to launch this program. And here we have a label
called launch program which you will see in a second. And we can see our program starts here. So in the source folder we
have index.ts. TS and our output files are stored in our workspace in our project in files with the JS extension.
Now here we need to add one more setting. It's called pre-launch task. We're going to set this to a string with
this value tc colon space build spacey space tsconfig.json. Make sure to type this
out exactly as I've shown you here. All these spaces matter. Okay. So with this setting, we're telling VS Code to use
the TypeScript compiler to build our application using this configuration file. Okay. Now we go back to index.ts
and to start debugging, we can go to the debug panel and click on launch program. This is the label that I just showed
you. Now look at the shortcut. It's F5. I always prefer to use shortcuts. So let's start
debugging. Good. Now our program started and the execution stopped right on this line at this break point. Now on the top
we have a bunch of tools for executing our code. We have step over for executing one line. We have step into
for stepping into a function. Currently we don't have any functions so it's not useful. We also have step out. This is
useful for stepping outside of a function. We have restart and stop. So in the tool tip that you see in front of
each tool you can see the shortcut. So the shortcut for stepping over a line is F10. Let's press that. So this line was
executed. Now on the left side under variables you can see all the variables that are detected in this debugging
session. So under local we have age that is set to 20. As we execute each line you will see the value of this variable
getting updated. Now, if there's something that you don't see in the variables window, you can go to the
watch window and insert a watch. So, we're going to watch the age variable.
Currently, it's 20. So, let's step over this line. Now, we're on this line. So, let's step over this as well. Now, age
got updated. But because our program terminated, we don't see its value anymore. But if you add one extra line
here, let's say console.log age. Let's start the debugging session one more time by pressing
F5. Now step over F10, F10, F10. And now H is 30. So this is debugging in VS Code. It's very useful when things go
wrong. We can start our program and execute it line by line. All right. Now that we're done with debugging, we can
stop the debugging session right here. So that brings us to the end of this section. Starting from the next
section, we're going to explore TypeScript in detail. Welcome back to another section of the
ultimate TypeScript course. In this section, we're going to explore the fundamentals of TypeScript. So, you will
learn about the built-in types like the any type, arrays, topples, enums, functions, and objects. The concepts
you'll learn in this section will be the foundation for future sections. So, don't skip any lessons. Now, let's jump
in and get started. [Music] So, JavaScript has built-in types like
number, string, boolean, null, undefined, and object. Now, TypeScript extends this list and introduces new
types like any, unknown, never, enum, and topple. As we go through this section, you will learn about all these
types in detail. But before we get started, let's see how we can play with primitive types in Typescript. So back
in index.ts, I'm going to delete all this code and declare a variable called sales of type number and set it to a
value like 1 2 3 4 5 6 7 8 9. So once again, we're annotating or explaining the type of the sales variable using
this syntax. Okay. Now, in Typescript, if you have a large number, we can separate its digits using an underscore.
that makes our code more readable. Okay. Now, let's also declare a variable called course of type string and set it
to TypeScript and one boolean called is published. Now, let me show you
something really cool. In Typescript, we don't always have to annotate our variables because the TypeScript
compiler can infer or detect the type of our variables based on their value. For example, because we have initialized
this variable to a number, the TypeScript compiler knows that this variable is a number. So, we don't have
to annotate it. So, if you remove the annotation and hover our mouse over this variable, look, we can see that sales is
a number. Similarly, because we have initialized course to a string, we can remove the annotation and once again we
can see that course is a string. And one more time. Now what if you declare a variable like level and don't initialize
it. TypeScript assumes that this variable is of type any. We'll talk about that in the next
[Music] lesson. In TypeScript, we have a new type called any which can represent any
kind of values. So if we declare a variable and don't initialize it, the TypeScript compiler assumes this
variable is of type any. So we can set it to a number and then later on we can set it to a string. But this is against
the whole idea of using TypeScript because we use TypeScript for it type safety. So we get type checking. So if
we use the any type, we essentially lose that feature and the major benefit of using TypeScript. So as a best practice,
you should avoid using the any type as much as possible. Let's look at another example. Let's say we have a function
called render that takes a document and simply renders it on the console. Now look, we have a compilation
error and the error is saying parameter document implicitly has an any type. Implicitly means we haven't explicitly
or clearly set the type of this parameter. So the compiler is inferring or guessing the type of this parameter
and that's why we have this error. Now let's say this is part of a JavaScript project. We're trying to convert to
TypeScript. And at this point, it's impossible for us to explicitly annotate this with a particular type. So we have
to use the any type here. Now here we have two options to turn off this error. One option is to annotate this with any.
So we're telling the compiler, hey, I know what I'm doing. The document is of any type. But what if we have tons of
errors of this kind? We don't want to go to every function and explicitly annotate various parameters with any.
Well, there is a nuclear option for that, but I don't personally recommend that. But let me show you how that
works. So, let's remove the annotation. Now, if you're on Mac, press command and P. If you're on Windows, press control
and P to bring up the search box and go to tsconfig.json. Now, look in the type
checking section, strict is turned on. And this is equivalent to turning on some of the basic type checking
features. You will learn about these settings as we go through the course. Now the first setting you see here is no
implicit any. So let's remove the comment. So if this feature is turned on, the compiler will complain about
implicit any types. Now let's turn this off and see what happens. So back in index.ts,
error is gone. So use this with caution only if you know what you're doing. Otherwise, there's really no point using
TypeScript because you will lose the major benefit of TypeScript. Okay, so back to TS Config. I'm going to revert
this code. Good. So, that's all about the any type. Next, we're going to talk about
[Music] arrays. All right, let's talk about arrays. In JavaScript, we can declare an
array like this. Now the thing about JavaScript arrays is that each element can be of different
type. So here we can have two numbers followed by a string. And this is totally valid JavaScript code because
JavaScript arrays are dynamic. So each element can be of different type. But what if we pass this array to a function
that expects a list of numbers? Then the third element is going to cause some issue. Right? This is where we use
TypeScript. So we can explicitly apply a type annotation here and say numbers is a number array. Now we see the error
immediately at compile time. So let's fix it. Good. Now in this particular case, we don't even have to apply the
type annotation because every element in this list is a number. So if you remove the type annotation, the compiler can
still infer or guess the type of this variable. Great. But what if we had an empty array here? Look, now the type of
this variable is any array, which is something we should avoid. With any arrays again, we can have a mix of
different types. So the first element can be a number. The second element can be of a different type like a string or
a boolean. So if you want to use an empty array, you have to explicitly apply a type annotation here. Let's say
number array. Now the third line is invalid. So let's delete it. Now let me show you another cool benefit of using
TypeScript and that is code completion or IntelliSense. So if you type numbers for each and pass an arrow function here
like n goes to and then type n period over here we can see all the properties and methods of number objects because
our editor knows the type of n it offers code completion. So we can see all the methods of number objects. This is very
useful. It's a great productivity boosting feature. We don't get this with plain JavaScript, right? So that's
another benefit of using TypeScript. That's all about the arrays. Next, we're going to talk about
[Music] topples. TypeScript has a new type called topple, which is a fixed length
array where each element has a particular type. We often use them when working with a pair of values. For
example, let's say for each user, we want to represent two values, an ID and a name. So, we declare a variable and
annotate it using a special syntax like this. First, we add square brackets and then tell the compiler that the first
element is going to be a number whereas the second element is going to be a string. And then we initialize our
variable like this. So, we have a fixed length array with exactly two elements, nothing more, nothing less. So if you
add a third element here, we get a compilation error saying type number string number is not assignable to type
number string. Okay. So exactly two elements and these elements should have these types. The first one has to be a
number. So if we pass a string again we get a compilation error. Now just like before here we get intellisense or code
completion. So if you access the first element, we see all the methods of number objects. And if you access the
second element, we see all the properties and methods of string objects. Again, one of the reasons I
love TypeScript. Now, one thing you need to know about topos is that internally they're represented using plain
JavaScript arrays. So if we compile our code, we're just going to see a regular JavaScript array. Let me show you. So
here in the terminal, let's run TSC. Good. Now back in our project here in this
folder let's open index.js look we just have a regular JavaScript array. So that means back in our TypeScript code if we
type user period over here we can see all the methods of array objects. Now one of this method is a little bit
troublesome and that is the push method. So we can call this method and store a third value in this array and the
compiler is not going to complain here. I believe this is one of the gaps in Typescript at the time of recording
this. Hopefully, this will be solved in the future. So, a tpple is a fixed length array where each element has a
particular type. Now, as a best practice, I would say restrict your tpples to only two values because
anything more than that is going to make your code a bit hard to understand. For example, if you add a boolean and
another number here, what do these values really represent? It's really hard to tell. So
topples are useful when we have only two values like key value pairs. Next we're going to talk about
[Music] enums. Typescript has another built-in type called enum which represents a list
of related constants. If you know C# or Java, you have seen enums before. We have the exact same concept here in
Typescript. So let's say we want to represent the size of t-shirts as constants. One way is to define three
constants like this. Small, medium, and large. That's one way. And then throughout our code, we
can reference this constant. Another way is to group this constant inside an enum. So we use the enum keyword. Then
give our enum a name. Now note that here I'm using Pascal naming convention. So the first letter of every word should be
uppercase. Okay. Then we add curly braces and inside the braces we add our members small, medium and large. Once
again I'm using the Pascal naming convention for the members. Now when we define an enome we don't need to define
these three constants anymore. And by default the TypeScript compiler assigns the first member the value of
zero and other members values like one two and so on. Now if you don't want to use these values we can explicitly set
values. So we can set small to one and then medium becomes two and large becomes three. We can also use string
values like S. If we do this then we have to explicitly set a value for each member. So medium and large like this.
Now I'm going to revert this code and just rely on numbers. So I'm going to initialize small to one and rely on the
compiler to set the value for other members. So now that we have this new type, we can declare a variable like my
size of type size and set it to size dot medium. Okay, so this is how we can use enaps. Now let's see what happens if we
log my size on the console. So back to the terminal, let's run the TypeScript compiler. Good. And then use node to
execute our JavaScript code. So we go to the disc folder and pick index.js. So look, we see the numeric
value associated with our enum member. Okay. Now let's look at the generated JavaScript code in this file. So back to
the project. Here's our index file. Take a look. So the generated JavaScript code is pretty verbose. It's pretty lengthy.
Now you don't need to worry about understanding this. But let me show you a trick. Back to our TypeScript file. If
we define this enum as constant, the compiler will generate a more optimized code. So let's recompile our code and
then look at index.js one more time. Now we don't see all that code
anymore. We only have one line for setting my size to two. Again, you don't have to worry about the generated
JavaScript code. All I want you to take away here is that using an enum, we can represent a list of related constants.
And if we define our enums using the constant keyword, the compiler will generate more optimized code. Next,
we're going to talk about functions. [Music] Let's talk about how Typescript helps us
prevent common problems when working with functions. So, let's define a function called calculate tax and give
it an income parameter of type number. Now, let's hover our mouse over the function name. Look at the type of the
return value. It's void, meaning this function does not return a value. If you return a value here like a number, now
the type of the return value is number. So the TypeScript compiler has inferred the type of the return value for us and
that's great. But as a best practice, we should always properly annotate our functions. So all the parameters as well
as the return type should be properly annotated, especially if you're building an API for other people to use. So in
this case to annotate the return type we go after the list of parameters add a colon and specify the return type like
number or void if you're not going to return a value. So let's add number. Now this has an extra benefit. If you forget
to return a value or if you return a different kind of value like a string we get a compilation error immediately. So
as a best practice always properly annotate your functions. Now let's temporarily return zero. Okay, look at
the income parameter. This is an unused parameter. Now we have a compiler option for detecting is unused parameters. So
let's go back to our TS config file. In the type checking section, we have a compiler option called no unused
parameters. We have to explicitly turn this on because this setting is not part of strict setting. Now back to index.ts.
Look, we have a warning saying income is declared but its value is never read. So let's change this code and say if income
is less than 50,000 then we're going to return income times 1.2. Now here we have another
compilation error saying function lacks ending return statement and return type does not include undefined. It sounds a
bit cryptic, but what this error is saying is that if this condition is true, we're going to return a number.
Otherwise, as you know, JavaScript by default always returns undefined from our functions. And undefined is not a
number. Now, let me temporarily remove the annotation for the return type. Look, we have no compilation
error, but this function has a problem. If the income is greater than 50,000, we're going to get undefined from this
function and this may cause a bug in our application. The good news is that we have another compiler option for
detecting these kind of issues where we forget to return a value. So back to our TS config file again in the type
checking section, we have a setting called no implicit returns. Again, we have to explicitly turn this on because
this is not part of the strict setting. Okay. Now back to our code, we have a warning
saying not all code paths return a value. So now we can fix this problem and say otherwise if income is greater
than 50,000, we're going to return income times 1.3. But again, it's best to always annotate our functions
properly to prevent all these issues. So we should always return a number from this function. Now we have another
useful compiler setting for detecting unused variables. So if we declare a variable like x and don't use it in this
function, this is unused. Now using the typescript compiler, we can find these issues in our code. So back to ts
config, here is the setting no unused locals. With this feature enabled now back to our code, we have a yellow
warning saying x is declared but it value is never read. So now we can delete this and make our code cleaner.
So this is the basics of functions. Now let's add a second parameter here called tax year of type number. Now we have a
warning here saying tax year is declared but it value is never read. So let's change our condition to if tax year is
less than 2022 then return income times 1.2. So now with these two parameters, if you want to call this
function, you have to supply exactly two arguments. Nothing more, nothing less. So I'm going to pass 10,000 for the
income and 2022 for the tax year. If you add an extra argument here, we get a compilation error saying expected two
arguments but got three. If you have been coding in JavaScript for a while, you know that this is valid JavaScript
code. So JavaScript doesn't really care how many arguments we pass to a function. We can pass more or fewer
arguments than the number of parameters. But Typescript is very strict about this. So here we should pass exactly two
arguments. So let's remove that. But what if you want to make this optional? Let's say in some places we want to call
this function without supplying the tax here. Well, we can make this parameter optional by adding a question mark right
here. Now we have a compilation error on this line saying object is possibly
undefined. So if it don't supply the tax year by default undefined will be used and we cannot compare undefined with
2022. So here we have two options. One option is to use the old JavaScript trick. So we wrap this in parenthesis
and then using the or operator we give it a default value like 2022. So if you don't supply the tax here, this value
will be used otherwise the argument that we pass will be used here. Okay. Now I don't like this approach. There is a
better way. We can give this a default value. So instead of making it optional, we give it a default value
like this. Now we can call this function with or without a tax here. If we don't supply an argument for the tax here,
this value will be used. Otherwise, what we pass here will overwrite the default value. Okay. Now,
with this default value, we no longer need to use this ugly expression. We can simply compare tax year with 2022. So,
let's quickly recap what you learned in this lesson. As a best practice, always properly annotate your functions, all
the parameters and return types, and enable these three compiler options. No unused locals, no unused parameters, and
no implicit [Music] returns. All right, the last thing we're
going to cover in this section is objects. So, let's declare an employee object with an ID property. Now, you
know that in JavaScript, objects are dynamic. So, their shape can change throughout the lifetime of our programs.
So once we have an employee object then later we can give it a new property like name and this is totally valid in
JavaScript. But as you can see this is not valid in Typescript. The compiler is saying property name does not exist on
this type. So this type is an object with an ID property which is a number. So just like all the variables we have
declared so far the TypeScript compiler has inferred the shape of this employee object. So if you hover our mouse over
employee, we can see its shape. It's an object with an ID that is a number. Now just like all the variables we have
declared so far, we can explicitly apply a type annotation here. So after employee, we add a colon followed by
braces and inside the braces, we define the properties of an employee object. So every employee should have an ID that is
a number and a name that is a string. Now the previous error on this line is gone. But we have a new kind of error.
Let's find out. The compiler is saying property name is missing. So the reason we are seeing this is because every
employee should have these two properties. But while initializing this object, we haven't supplied the name
property. Now here we have two options. One option is to set the name to an empty string. We cannot set it to null
or undefined. We'll talk about this in the next section. So we should either set it to an empty string or we can make
this property optional and then we don't have to supply the name property while initializing an employee. Now even
though TypeScript allows us to do something like this, this is something we should avoid because conceptually it
doesn't make sense to have an employee without a name. Every employee should have a name. it would make sense to make
the fax property optional because not everyone has a fax machine, right? So, always make sure the code you write
conceptually makes sense. Don't blindly use features of TypeScript or any other programming languages. Okay? So, in this
case, we're not going to use an optional property and that means we should either set the name to an empty string or
initialize it right here. Okay? Now, we have an error because I used a period. Okay, so now we don't need this last
line anymore. Okay, now sometimes we want to make certain properties read only so we don't accidentally change
them later on. So with our current implementation, we can change the ID of an employee anytime and this is not
valid. This is where we use the read only modifier. So we apply it before the name of the property and now the
TypeScript compiler prevents us from accidentally modifying the value of this property. Okay, good. Now, how can we
define a function or more accurately a method in this object? Let's say every employee object should have a retire
method. So, in our type annotation, we need to define the signature of this method. We need to specify how many
parameters it's going to have, what is the type of each parameter and what is the type of the return value. So, we're
going to have a retire method with one parameter that is date. Now, here we use the arrow function syntax. So first we
add parenthesis and inside parenthesis we specify the parameters. So we're going to have a date parameter of type
date. This is just a built-in date object in JavaScript and TypeScript. So we only have one parameter. Then we use
a fat arrow. And here we specify the type of the return value. We don't want to return any values. So we're going to
use weight. Now you have a compilation error saying property retire is missing because when initializing this object we
haven't supplied the retire method. So let's do that real quick. All right. Retire is going to be
a method with one parameter and over here I just want to log the date on the console. Pretty
simple. So this is how we can work with objects. Now, I know this syntax, this type annotation is a little bit messy.
It's making our code a bit verbose or noisy. So, in the next section, I'll show you a better way to work with
objects. Welcome back to another section of the ultimate TypeScript course. In this section, we're going to explore
more advanced types in Typescript. So, you will learn how to use type aliases to reuse types and simplify your code.
how to use unions and intersections to combine types, how to narrow types, how to work with nullables, as well as the
unknown and never types. So, let's jump in and get [Music]
started. All right. So, this is the employee object that we created at the end of the previous section. Now, there
are three problems in this implementation. The first problem is that if you want to create another
employee object, we have to repeat this structure. We have to repeat this shape. So we'll end up duplicating our code
which is bad. We always want to confirm to the dry principle. Don't repeat yourself. Now the second problem is that
the other employee object might have other properties. So these two employee objects might not have a consistent
shape because we don't have a single place to define the shape of an employee object. Now the third problem is that
overall this structure is making our code a little bit hard to read and understand. This is where we use a type
alias. Using a type alias we can define a custom type. Let me show you. So on the top we start with a type keyword.
Then give our new type a name. And once again here we use the Pascal case. So employee with a capital E. Then we set
it to braces. And inside the braces, we define all the properties and methods an employee object should have. So I'm
going to go in this annotation, select these three lines, and by pressing alt and up, we can move these lines up.
Okay. Now when defining this employee object, we annotate it with our new type employee. Okay. So now we have a single
place where we define the shape of an employee object and we can reuse this in multiple places. This is the benefit of
using a type [Music] ads. All right, let's talk about union
types. With union types, we can give a variable or a function parameter more than one type. So let's define a
function for converting weight from kilogram to pounds. Now we give it a parameter called weight. Now let's
assume that this parameter can be either a number or a string. So we annotate it with number or string. So using a
vertical bar we can create a union type. Now we can call this function in two ways. We can give it a number or a
string like 10 kilogram. Okay. Now let's finish off this example. So we're going to annotate the return type and return a
number. Now in this function at this point we don't know if the weight is a number or a string. So if you type wait
period we only see the properties and methods that are common between numbers and strings. So both numbers and strings
have these three methods to local string to string and value of. So here we're not seeing methods that are specific to
numbers or strings. And this is where we use a technique called narrowing. So we're going to narrow down this union
type into a more specific type. So here we can say if type of weight equals number. Now in this block the compiler
knows that the weight is a number. So if you type weight period we see all the methods that are available in number
objects. And by the way we don't necessarily need the code blocks here in this line. If you have a oneliner and
type wait period we still see all the methods available in numbers. Okay. So what are we going to do here? We're
going to return weight time 2.2. Otherwise, if we end up here, that means weight is a string. So now we see
all the properties and methods of string objects. Okay. So to finish this example here, we're going to return. First we
need to convert the weight to an integer. So we call parsend pass the weight and then
multiply it by 2.2. Okay. Now, back to the terminal. Let's compile our code and see what we
get. All right, we're going to go to index.js. Take a look. Our union type is not part of the generated JavaScript
code. It's purely for the compiler to do it type checking. [Music]
So in the previous lesson you learned that using a union type we can say a variable or a function parameter can be
one of many types. Right? Now we have another technique for combining types called intersection. So instead of a
vertical bar use an amperand. And now this type represents an object that is both a number and a string at the same
time. Now technically this is impossible. We cannot have an object that is both a number and a string at
the same time. So let's look at a more realistic example. I'm going to use our type alias to define a new custom type
called dragable. So this represents an object that can be dragged on the screen. Now what properties or methods
do we need in a dragable object? Well, at least we need a method called drag which takes no arguments and returns
void. Now similarly we're going to define another type called
resizable. And here we need a resize method that should take two parameters like the new width and the new height.
But for simplicity let's get rid of all that noise and just add no parameters and say this method returns void. Okay.
So now we have two separate types and using an intersection type we can combine them into a new type. So we can
define a new type called UI widget which is dragable and resizable. So this is an intersection type. Now with this type in
place we can declare a variable called text box which is a UI widget. Now to initialize this object we need to
implement all members of dragable and resizable objects. So we need to implement the drag and resize methods.
So drag is a simple method and so is the resize method. So this is type intersection in
[Music] action. Sometimes we want to limit the values we can assign to a variable. This
is where we use literal types. So let's declare a variable called quantity and assume that the quantity can either be
50 or 100 but nothing else. Now here's the problem. If we annotate this with number, this can take any numbers, any
valid numbers in JavaScript, right? So to limit the values we can use here, we use a literal type. So instead of
annotating this with a type like number, we'll annotate it with a literal meaning an exact or specific value. So I'm going
to replace number with 50. Now quantity can only be set to 50, nothing else. So if you set it to 51, you get a
compilation error. Now you might think this is not useful. Well, that's true. But what if we apply the union operator
here? So we can say the quantity can be 50 or 100. Now we can set it to 50 or 100, right? But we're not done yet. We
can make this code even better. So instead of hard coding these literal values here, we can create a custom type
using a type alias. So we define a new type called quantity which can be 50 or 100. And then we annotate our variable
with our new type. So what we have over here is called a literal type. Now literals don't have to be numbers. It
can also be strings. For example, we can define a new type called metric which can be centimeter or
inch. Okay, so that's all about literal types. Next, we're going to talk about nullable
[Music] types. All right, let's talk about working with null values. So by default,
TypeScript is very strict about using null and undefined values because as you probably know, these values are common
source of bugs in our applications. So let's look at a real example. Let's define a function called greet that
takes a name which is a string. Now in this function, we just want to do a console.log and print
name.2 uppercase. Now in vanilla JavaScript we can call this function and accidentally give it
null or undefined. That is totally valid JavaScript code. But when we run our program our program is going to crash
because we cannot call this method on a null or an undefined object. So this is why null and undefined values are a
common source of problems and that's why by default the TypeScript compiler stops us from using null or undefined values.
So here we have an error saying argument of type null is not assignable to parameter of type string. Let me show
you where this comes from. So let's go to our tsconfig file. In the type checking section, we have a compiler
option called strict null checks that this is enabled by default as part of the strict option. So if strict is set
to true, strict null checks is also true. But we can overwrite it and turn off this feature.
Now back to index.ts the error is gone. So this is why by default TypeScript is very strict about using null values. So
technically we should never use this option. We should never turn it off. So I'm going to revert this back. But what
can we do in this function? What if we want to have the ability to use a null value? Perhaps if we don't have a name,
we want to print just a generic message like hola meaning hi in Spanish. So here we can say if name is truthy meaning it
has a value then we're going to print name to uppercase otherwise if it's null or undefined
we're going to print hola. Okay but we cannot pass a null value here. This is where we use a union type once again. So
we annotate this parameter with string or null. Now we can pass a null value but we cannot pass undefined because
this is not a valid value for this parameter. So if you want to have the ability to pass undefined as well again
we're going to use the union [Music] operator. Now when working with nullable
objects we often have to do null checks. For example let's define a type alias called customer.
and give it a birthday property of type date. Now let's define a function for getting a customer from a database. So
we give it an ID which is a number and we get either a customer or null in case there is no customer with the given ID.
Now in this function let's write some basic logic like return if id equals zero then we return null otherwise we
return a customer object with this birthday. Pretty simple. Now let's declare a variable and here we call get
customer and pass zero. Now let's imagine we want to print the customer's birthday. So console.log and here we
pass customer. thing. Now here we have a compilation error because customer might possibly be
null. So as you know one way to solve this problem is like this. We check if customer is not null then we execute
this piece of code. Now let's take this to the next level and assume that under certain circumstances this function
might return undefined. So we add undefined here. And now we have to expand this if statement and check for
undefined. So if customer is not null and it's not undefined either then we print the customer's birthday. That
works but there is a simpler way to write this code in Typescript. We can remove this if statement and use what we
call the optional property access operator. So because customer might be null or undefined right after it we add
a question mark and then we add the chaining or the dot operator. So this question mark followed by the chaining
operator is called the optional property access operator. So now this piece of code gets executed only if we have a
customer that is not null or undefined. Let's see this in action. So we're passing zero and we get a null object.
So if we execute this code, we get undefined. So the result of this expression is undefined. But if we pass
one, we get an actual customer object. So we'll see the customer's birthday on the console. There you go. Okay. So this
is optional property access operator. Now we can take this to the next level. Let's make the birthday property
optional. Now let's say we want to print the full year of the birth year. So over here we
have to call dot get full year. Now once again we have the same compilation error because the birthday property might
possibly be undefined. Once again to solve this problem we can use the optional property access operator. So
now this piece of code gets executed only if we have a customer and that customer has a birthday otherwise the
result of this expression is undefined. Okay. Now we also have what we call optional element access operator and
this is useful when we are dealing with arrays. So we might have an array of customers and we want to print the first
customer on the console. Now if this array is going to be null or undefined of course we have to check if customers
is not null and it's not undefined then we're going to access the first element. Now this is where we can use the
optional element access operator. So before accessing this element we use a question mark and a dot. Okay. Now we
also have the optional call operator which has the exact same syntax. So let's
imagine we have a variable called log which is going to reference a function. So for simplicity I'm going to annotate
this with any might be set to a function like a function that takes a message and prints that message on the console or we
might set this to null. Right now let's say we want to call this and pass some value. If we run this program, our
program is going to crash because log is null. So this is where we can use the optional call operator. So this piece of
code will get executed only if log is referencing an actual function otherwise will get
[Music] undefined. When working with null or undefined values, sometimes we need to
fall back to a default value. For example, let's declare a variable called speed of type number or null. Let's
imagine that we receive this from the user. For now, I'm going to initialize this to null, meaning the user didn't
provide a value for the speed. So, we get the speed and then we create a right object. And we could annotate this with
a proper type, but let's not worry about it at this point. We just create a write object and set the speed to either the
value we received from the user or a default value. In plain old JavaScript, we can implement this by saying speed or
30. So if speed is truthy, we're going to use that value. Otherwise, if it's falsy, we're going to use 30 as a
default value. Now, I have a question for you. What are falsy values in JavaScript? We
have undefined, null, empty string, false, and zero. Now, what if zero is a valid value for the speed? With our
current implementation, if the user enters zero, zero is going to be ignored because it's a falsy value and we're
going to use 30. So, a more accurate way to implement this scenario is by checking for null. So we can check if
speed is not null then use speed otherwise use 30. Now in Typescript we have a better way. We can simplify this
expression to speed double question mark 30. What you see here is called the knowledge qualising operator. So the
reason this is called knowledge is because this value can be null or undefined. So with this we're saying if
speed is not null or undefined use that value otherwise use 30 as a default [Music]
value. Sometimes we know more about the type of an object than TypeScript. Here's a real example. In JavaScript we
have this document object that represents a document or an HTML page loaded in a browser. Now in this object
we have a method called get element by ID for getting a reference to an element on this page. Now look at the signature
of this method. Here we have one parameter called element ID which is a string and the method returns either an
HTML element or null. So nothing new so far. So let's imagine on this HTML page we have an input field called phone. So
we pass that as a string and then get a reference to that input field. So let's store it in a variable called phone. Now
let's look at the type of phone. It's either an HTML element or null in case that element was not found. But let's
imagine that we do have an element by this name. So the type of phone is definitely HTML element. Now HTML
element is a class defined in JavaScript that represents any kind of HTML elements. It's like the base or parent
class for other types of elements. So we have more specific elements like HTML, input elements and so on. Now elements
of this type have an extra property called value for reading the value entered by the user. However, if we type
phone value, we cannot see that property. Yes, we have other properties like area value max or value min and so
on. But none of these properties represent the value entered by the user. This is where we use a type assertion.
So we're going to tell the TypeScript compiler, hey, I know more about the type of this object than you. So after
this expression, we type as and then specify the target type HTML input element. Now the type of phone is HTML
input element. And if you type phone period, we can see the value property. So this is type assertion in
action. Now in some other programming languages like C# you also have the ask keyword but the ask keyword functions
differently in those languages. In Typescript the ask keyword doesn't perform any type conversion. So it's not
going to convert the object that is returned from this method to a different type of object. This is purely for
telling the compiler that we know more about the type of this object. So if the object that is returned from this method
is not an HTML input element, this line is not going to raise any exceptions and instead when we try to access this
property, the value property, our program is going to crash. So be aware of this. There is no type conversion
happening under the hood. Now there is another syntax to use a type assertion. So instead of using the as keyword, we
prefix this expression with angle brackets. And here we add the target type HTML input
[Music] element. Earlier in the course, we talked about the any type which
represents any kind of value and I told you that we should avoid using it as much as possible. But let's say we're
converting some JavaScript code to TypeScript and we have a situation where we really have to use the any type. So
let's define a function called render that takes a document. Now the compiler is complaining about the type of this
parameter. So to make it silent, we use the any type. Now there is a big problem with the any type here. We can call
document.ove. We can call document.fly. We can call document whatever we want. and there is no type
checking done. So if you run our application and there is no move method on this document object, our program is
going to crash. This is where we use another similar type called unknown. So if you change the type of this parameter
from any to unknown, we immediately see this compilation errors. So the TypeScript
compiler is saying object is of type unknown. So the compiler doesn't know about the type of the document. It
doesn't know if there's a move, fly, or other methods on this object. Now, this is where we use type narrowing.
Remember, we talked about narrowing early in the course. So, by using a type guard, we can narrow down the type of an
object and get more specific. So, here we can use a type guard like if type of document equals string. Now, in this
block, we have access to all string methods. So if you type document to uppercase, we don't get a compilation
error because the compiler knows that this document is a string object. Now this type of operator only works for
primitive types like string, boolean, number. If you have custom objects created with classes, we have to use
another operator called instance of. So we can say document instance of and then here we add our custom type like word
document. We'll talk about this operator in the next section where we talk about classes and interfaces. For now, all I
want you to take away is that using the unknown type is preferred to using the any type because the compiler forces us
to do some type checking to make sure the methods we're calling exist on the target
object. All right, the last thing we're going to cover in this section is the never type,
which represents values that never occur. Quite frankly, it's not a type used that often, but let's look at a
couple of examples. So, let's define a function called process events. Now, let's say we want this
function to run continuously and process a bunch of events. Perhaps it's constantly watching a message queue,
waiting for the next event. So here we can implement an infinite loop like this and inside this loop we can read a
message from a que. Now the detail of this is irrelevant. We don't care about that. What matters is that if we call
this function and then right after we do something else let's say print something on the console. You know that this line
will never get executed because this function never returns because here we have an infinite loop. Now, this is
where we can annotate the return type of this function with never to tell the compiler that this function never
returns. Now, look at the last line. It's grayed out, meaning it's not going to get executed. But there's more to
this. Let's go to our TS config file. Once again, in the type checking section, we have a compiler option
called allow unreachable code. So, let's uncomment this line and turn off this feature. So we're not going to allow
unreachable code. Now back in index.ts. Look, now we have a compilation error saying unreachable
code detected. So this is the benefit of using the never type. Both developers and the compiler can reason about
sections of code that are unreachable. Now let's see what happens if you remove the annotation. So we remove it and let
the compiler infer the return type. Look, the return type is void. And now this line appears to be reachable
whereas it's not. So this is where we should use the never type. Okay. Now let's look at another
example. I'm going to define another function called reject that takes a message of type string. And here we
always throw a new error. Again we have a function that will never return. It always throws an
error. So if we call this function over here reject with some message this line will throw an
exception and the last line will never get executed. But we don't see that here in code because by default the compiler
infers the return type as void. But if you annotate this function with never, now we can see that the last line will
never get executed. So this is the never type. As I said, it's not something that we use that often, but be aware of it.
And that brings us to the end of this section. In the next section, we're going to talk about classes and
interfaces. Welcome back to another section of the ultimate TypeScript course. In this section, we'll be
talking about object-oriented programming with TypeScript. We'll start this section by a quick introduction to
object-oriented programming. Then we'll talk about creating classes which are the building blocks of object-oriented
applications. You will learn all about creating constructors, properties and methods, access control keywords,
getters and setters, static members, and creating dynamic properties using index signatures. Then we'll talk about
inheritance, which is a technique for reusing code. Next, we'll explore a powerful technique called polymorphism.
And we'll finish this section by exploring abstract classes and interfaces. So there's a lot we're going
to cover. Now let's jump in and get [Music] started. So what is object- oriented
programming? Well, object- oriented programming is one of the many programming paradigms or styles of
programming. We have many paradigms like procedural, functional, object-oriented, event-driven, aspect oriented and so on.
These programming paradigms are styles or ways of writing code. They're not programming languages. Different
programming languages support different programming paradigms. For example, JavaScript and TypeScript both support
some object-oriented and functional programming techniques. In object- oriented programming, objects are the
building blocks of our application. So a real application consists of hundreds or even thousands of objects working
together to solve problems. An object is a unit that contains some data also called state and operations also called
behavior. For example, we can represent a person as an object. In this object, we can store some data like the person's
name, email, and so on. These are the properties of a person object. They are like variables that only exist inside a
person object. Now in this person object we can also have some operations or some behavior like talk and dance. These are
essentially functions inside a person object. In object- oriented programming we refer to these functions as methods.
So a method is a function inside an object. Similarly, in a real application, we can have an object
representing a login form as well as objects for authenticating users, sending emails and so on. So each object
is responsible for a single task. Kind of like how people work together in the real world. For example, in a
restaurant, we have different people each responsible for a single task. You have a hostess, a chef, a server, and
many other roles. Right? So that's the idea of object-oriented programming. Now, object-oriented programming is
often compared with functional programming, which is a completely different style of programming. So, in
functional programming, functions are the building blocks of our applications. And for that reason, our code would end
up looking different. Now, some people like our famous old John Smith hate object-oriented programming and are
obsessed with functional programming. So, they try to solve every single problem using functional programming.
Don't be one of them. Remember, every programming paradigm or every tool in general has some strengths and some
weaknesses. So there is no such a thing as the best tool, the best language or the best programming paradigm in the
world. So please, for God's sake, stay away from this arguments about functional versus object-oriented
programming. And more importantly, don't fall in love with just one tool and try to solve every single problem using that
tool. So, in this section, we'll be exploring the fundamental object-oriented programming techniques.
But I got to tell you, that's just the tip of the iceberg. There is way more to object-oriented programming than we can
cover in this course. If you really want to understand object-oriented programming, I have a three-part course
called design patterns that teaches you how to solve real coding problems using object-oriented programming techniques.
In this course, I've used Java, but you can still follow along because the syntax or the grammar of TypeScript is
almost identical to Java. So, you don't need to learn Java just to take that course. All right, that's it for now.
Next, we're going to talk about creating classes in [Music]
Typescript. So, object-oriented programming is all about objects. Now to create an object first we need to create
a class. A class is a blueprint for creating objects. It's like an object factory. So let's say we want to
represent the concept of a bank account. So we need a class for that. Now in this class we can have properties like ID,
owner and balance and methods like deposit and withdraw. So let's implement this class in Typescript. We start with
a class keyword. Then give our class a name like account. And once again note that here I'm using the Pascal naming
convention. So we capitalize the first letter of every word. Okay. Then we add braces. Now here we define our
properties. So we have ID which is a number. Then we terminate this line using a semicolon. Similarly we have
owner which is a string and balance which is a number. Now here we have a few compilation errors. Let's take a
look at one of them. So the error is saying property balance has no initializer and is not definitely
assigned in the constructor. Basically the error is complaining that this property is not initialized. So to solve
this problem here we need to create a constructor. A constructor is a special function or a special method inside a
class that is used for initializing an object. So we start with constructor. Now just like defining a method here we
add our parameters. So here we need three parameters. ID which is a number, owner which is a string and balance
which is a number. Now this method cannot have a return type annotation because it should always return an
instance of a bank account. So if you add braces here and hover our mouse over constructor, look this constructor
always returns a bank account. Okay. Now in this method we type this to reference the current class. Then we type a period
and now we can see all the properties we defined earlier. So we set ID to the id that we received over here. Similarly we
set this ownoner to owner and this balance to balance. So this is how we can initialize our properties. Now in
this class we can also have methods. So let's implement the deposit method. So after the
constructor we add deposit which takes an amount which is a number and returns void. So we're defining a function but
this function is called a method because it's inside a class. Okay. Now in this method we can have some logic like if
amount is less than zero or even less than or equal to zero then we're going to throw a new error and say invalid
amount. Otherwise, if the amount is correct, we're going to take that amount and add it to the balance. So, we say
this.balance plus equal amount. Okay, so this is how we can create a class in Typescript. Once we have a class, we can
create an object using that class and we're going to talk about that in the next lesson. But before we get there,
let's quickly compile our code and see what we get. So, here in the terminal, let's run
tscim. Good. Now, let's look at index.js. JS. All right. Look, we have a class
called account. But in this class, we don't have any properties. So the properties that we defined over here are
only for TypeScript. They don't exist in JavaScript. In JavaScript, we only have this constructor for initializing this
properties. And obviously, we don't have any type annotations. So we don't see the type of this properties or the
annotations for the deposit method. Everything else is exactly the same. So this was our first class. Next, I'm
going to show you how to create an object using this [Music]
class. So, we created the account class. Now, to create an object, we go after this
class and declare a variable called account and set it to a new instance of the account class. So
using the new operator we can create an instance or an object from an existing class. Then we open parenthesis just
like calling a function. Now look what you see here is our constructor. So here are the parameters we defined earlier.
ID, owner and balance. So let's pass some values here for initializing this bank account. Marsha as the owner and
zero for the balance. So now we have an object. Now to use this object we type account period and here we can see all
the members or all the properties and methods of this object. So we have balance deposit ID and owner. Now the
members with the blue icon are the properties and the members with the purple icon are methods. Okay. So here
we can call the deposit method and deposit $100 in this account. And of course if we pass a negative value we're
going to get an error. So let's deposit $100 and then log account.balance. Now here in the
terminal, let's compile our code and then run it using note. So note this /index.js. Good. So we can see our
balance. Perfect. Now let's see what happens if we print the account object itself. So back to the terminal. Now
here we can combine these two commands in one step. So we type tsc double amperand then
node/index.js. I'm not entirely sure if this works on Windows. So if it doesn't, please excuse me. You have to do your
own research to find how to combine two commands on Windows. Let's go ahead. So here we can see all properties of our
account object. We have ID, owner, and balance. Beautiful. Now earlier we talked about the type of operator. So we
use that when we talked about unions. Remember I talked about narrowing down a union using a type guard. So we had some
code like if type of some object equals number then we had some code after remember. Now let's see what happens if
we print type of this account. So back to the terminal let's go ahead we see object. So the type of operator always
returns object no matter what the underlying class is. So whether our object is an instance of a bank account
or a person or a customer, we always see an object. But what if we want to check the type of an object and see if it's an
instance of a given class like account or person? Then we need to use the instance of operator. So here we type
account instance of account. Now what we have here is a boolean expression. So it produces a boolean value. Now let's run
our program one more time. we get true. Great. So if you're using a type guard to narrow down a type and
you're dealing with a custom object, you should always use the instance of operator instead of typo. Okay. Next,
we're going to talk about read only and optional properties. [Music]
In Typescript, we have modifiers that we can apply to our properties and this help us write more robust code. For
example, let's say the ID of a bank account should never change. With our current implementation, we can change
the ID of a bank account anywhere in our code. For example, we can go in our deposit method and accidentally set this
ID to zero. And this may create a bug in our program. Now similarly we can go outside of this class and where we
create an object we can set account ID to zero again something that should never happen. Now we can solve this
problem using the read only modifier. So on the top we prefix this property with the read only keyword. Now we can only
set it in the constructor and if we try to reset it anywhere else we get a compilation error. So here the error is
saying cannot assign to ID because it's a read only property. Good. Now let's remove this
line. Now let's define a new property called nickname of type string. Now we have a compilation error because we
haven't initialized this property. But let's assume that not every bank account can have a nickname. This is an optional
name or an optional label that the user attaches to their bank account. So we don't want to initialize it in the
constructor. Now what can we do? We can make this optional by appending a question mark here. Now the error is
gone. So this is all about read only and optional properties. In the next lesson, we'll talk about other modifiers we can
apply to our properties to write more robust [Music]
code. In TypeScript, we have other modifiers for controlling access to properties and methods. And again we use
these properties to write more robust code. So let's look at a real example. In our current implementation we have
the deposit method for updating the balance. Now let's say in the future we also want to record transactions in this
account. So every time we deposit or withdraw money, we want to record a transaction so we know who paid how much
when. Okay. So we're going to have a new property in this class called transactions which is going to be an
array of transaction objects. Then every time we want to update the balance just before doing so we're going to record a
transaction object. Okay. Now there is a problem with this implementation. The problem is that once we have an account
object we can directly update its balance. Why is this a problem? Because with this line of code, we don't have a
record of a transaction. We don't know who paid how much money when. This is where we can use access control keywords
or access modifiers to solve this problem. In Typescript, we have three access modifiers: public, private, and
protected. We'll talk about protected later in this section. So for now, let's just focus on public and private. Now
when we declare properties, all these properties are public by default. So we don't have to use the public keyword
right here. But if we use the private keyword, look now we get a compilation error saying property balance is private
and only accessible within class account. So we cannot access this property from outside of the account
class but we can access it from within this class inside the deposit method. This is the benefit of using a private
property. Now a common misconception by a lot of beginners is that we use private properties for restoring
sensitive data like user's password, credit card number and so on. Don't ever do this. We only use private properties
for writing robust code because this private modifier enforces some rule in our code. Okay. Now by convention, we
prefix these private properties with an underscore. So let's press F2 here and rename this property to underline
balance. That's better. So now we cannot access the balance property. But what if you want to show the balance to the
user? If we type console.log account.balance, this doesn't work. So how can we solve this problem? Well,
here's one simple solution. In this class, we can define a method called get balance which returns a number. And here
we return this underline balance. Then we call this method right here. Now there is a better way which
we'll talk about soon. Now this access modifiers can also be applied to methods. For example, in this class,
let's define a method called calculate tags. Let's say we're going to use this method only internally within this
class. Perhaps we're going to call it from the deposit method. So we don't want to make this accessible from the
outside. So now we can apply the private keyword here and with this if we type account dot we cannot see the calculate
tax method. Okay. So this is all about access modifiers. Next we're going to talk about parameter properties.
[Music] Let me show you a cool feature of Typescript that helps us write more
concise code. So here we have a constructor with three parameters and three lines of code for initializing the
related properties. So every time we create a class in Typescript, we have to repeat this pattern. We have to create a
constructor with a bunch of parameters and write the property initialization code in that constructor. This is very
repetitive. Let me show you a better way. Over here when we declare this parameter we can prefix it with an
access modifier like public or private and this tells the compiler to create a property by this name and initialize it
in one go. So now we can comment out this line and the initialization code. The compiler will do this for us.
There's just one thing over here. We declared this property as read only. So we need to add the read only modifier
after the access control modifier. Okay. Now similarly we're going to make owner public and with this we can remove this
line and the initialization code. And lastly the balance. Now balance is going to be private and we're going to call it
underline balance. So let's comment out this line and this line as well. Now let's remove all this unnecessary code.
So going forward, this is the syntax we're going to use for creating constructors. It's much more concise.
Now, this line is getting a little bit too long. So let's break it down into multiple lines. So always pay great
attention to formatting your code so both you and other people can easily read your code. So here we can put each
parameter on a separate line and this makes our code much more readable. Great. So these are called parameter
properties. Next we're going to talk about getters and [Music]
setters. So earlier we implemented this method for getting the balance and I told you that there is a better way. Now
don't get me wrong, this solution is perfectly fine but I don't like the fact that we are calling a method here.
Wouldn't that be nicer if you could code like this? So we will access the balance property just like before. This is where
we use getters and setters. So look earlier we renamed this private property to underline balance. So now we can put
the cursor here, press F2 and rename this method to balance and then we apply the get keyword just before it. Now what
we have is called a getter. So a getter is a method inside a class that we use for getting the value of a property. Now
with this syntax we can access the balance property just like before but we cannot set it. So if we try to set
accountbalance to some value we get a compilation error saying cannot assign to balance because it's a readonly
property. Now in this case we don't really want to assign directly to balance because as I said before the
balance should only be updated as part of depositing or withdrawing money. But let's say in some situations we want to
implement a setter where we get a value and validate it. So let me show you how to implement a setter here. So we start
with a set keyword. Then we give our method a name just like before balance. Now this method should get some value
which is going to be a number. And in this method we can do some validation. For example, we can say if value is less
than zero, throw new error invalid value. Otherwise, if the value is correct, then we can set this underline
balance to this new value. So, we have a getter for getting the value of this private property and a setter for
setting its value. Now, with this syntax, we can work with this property just like before, but we are not
assigning directly to our private property. So anytime we assign a value to this property, this setter gets
called and this is our chance for validating data. Now again in this case we don't want to have a setter. So I'm
going to delete this method. Okay. So this is all about getters and setters.
[Music] So you know that in JavaScript we can create an object and add properties to
it dynamically. But this is not possible in Typescript because TypeScript is very strict about the shape of objects. But
there are situations where we need to add properties to an object dynamically and this is where we use index
signatures. So let's say we're building a ticketing application for concerts and for each concert we want to know who is
sitting where. So we start by creating a class called seat assignment. Now in a venue we can have
seats like A1, A2 and so on. And we want to know who is sitting on each seat. So we can say Mosh is sitting on A1, John
is sitting on A2 and so on. Now we don't want to define individual properties like A1, A2 and so on here because first
of all this is very repetitive. What if we have a 100 or a thousand seats in a venue? We are not going to define a
thousand properties here. Right? Also what if in another venue our seats are numbered differently. So we don't have
A1 A2. We have some other numbering system. So this is where we use index signatures for creating properties
dynamically. So look instead of typing the name of a property like A1 or A2 we add square brackets give our property
name like seat number of type string. Why am I using string here? Because we can represent the property name like A1
or A2 as a string. Okay. Now what kind of value can we store in this property? In this example I'm using strings. So we
can store a string like mosh hamadani. But we can store anything here. we can store a person or customer object. Okay,
so for now let's just set the type of this property to string. Now let's remove these individual properties. So
what we have here is called an index signature property. It's a special kind of property. Now let's create an object.
We can call it seeds. And with this we can set seeds a1 to mosh seeds a2 to john and so on.
Okay. Now in javascript and typescript as well we have another way to access a property. So here we're using the dot
notation but we can also use the square bracket notation. So we can say seats square brackets a1 equals mosh. So these
two lines are identical. Okay, so using index signatures we can create properties dynamically just like
JavaScript but we also get type checking. We get type safety. So we know that in our current declaration we can
only store string values in this properties. So if I try to set a2 to a number I get a compilation error. So
that's all about index signatures. Next we're going to talk about static members.
[Music] Now let's imagine we're building a ride sharing application like Uber. So people
can use their phone and request a right. So we create a class called ride. Now in this class we can have properties like
passenger as well as pickup location, drop off location and so on. But let's not worry about these properties and
talk about keeping track of the active rights. So we want to know how many active rights we currently have in our
system. So we define a property called active rides of type number. Now we get a compilation error because we haven't
initialized this property. So here we can create a constructor or we can initialize this property directly here.
Good. Now in this class we're also going to have methods like start and stop. So this is where we increment this active
rights and stop where we decrement this active rights. Great. So we have a right class. Now let's create two rights. So
write one is going to be a new right and here we call write one start. Now I'm going to duplicate these two lines and
change right one to right two. Okay. Now finally let's log the number of active rights on the console. So log right
one.active rights and write two active rights. Great. Now here in the terminal let's compile and run our
program. So we see one but we currently have two active rights. Why is this happening? Well, here we're dealing with
two separate objects, right one and right two. And each object is in a separate space in memory. So each object
is independently tracking the active rights. That is why we have this issue. What we need here is a single or global
place where we can keep track of the active rights. And this is where we use static properties. A static property is
a property that belongs to a class and not an object. So we're going to have only one instance of that property in
memory. So on the top where we define our property, we're going to prefix this with static. Now this property belongs
to the right class and not a write object. So down below look when we try to get right one.active rights, we get a
compilation error saying property active right does not exist on type right. Did you mean to access the static member
writees instead? That is correct. So we're going to replace right one with the right class. And one more time.
Yeah, these two lines are the same. So I'm going to delete one of them. So now this property belongs to the right class
and that is why we also have two compilation errors because in the start method this references the current
object but an instance of the right class does not have a property by this name. So once again we need to replace
this with write. Okay. Now let's run our program one more
time and see what happens. Okay. We see two active rights. Beautiful. So this is how static properties work. But there is
a problem in this implementation. The problem is that anywhere in this code I can directly modify ride.active rights.
So I can set it to 10 and this is going to create a bug in our program. Right? So once again we're going to use access
modifiers. So we're going to make this private so we cannot access it directly. And then we're going to create a public
getter of reading its value. So in this class we're going to define a getter called active rights. Now I forgot to
rename this property to underline active rights. Good. Now this is our getter. So it's going to be a method that
returns write underline active rights. Okay. Now this method this getter is currently part of a write object. That's
why we have a compilation error here saying property active rights does not exist on type right. So to move this to
the right class we have to prefix this with the static keyword as well. Now let's look at the error one more time.
This time the error is saying cannot assign to active rights because it's a read only property. Great. So now this
can prevent us from accidentally creating bugs and we have a single place where we can keep track of the active
rights. So when we make a property or a method static that property or method becomes part of a class and we'll have
only a single instance of them in memory. [Music]
Sometimes we deal with classes that have some commonality. For example, both student and teacher can have common
properties like first name, last name, and full name that combines the two as well as methods like walk and talk. Now,
we don't want to write this code in two separate places, once in the student class, once in the teacher class because
duplication in code is bad. It's better to write this code once and reuse it in many different places. And this is where
we use inheritance. So we extract these common properties and methods and put them in a separate class like person and
then have student and teacher inherit this commonalities. This is inheritance in object-oriented programming. So
inheritance is a mechanism that allows us to reuse our code. Now in this scenario we refer to the person class as
the parent or the base or the superass and student and teacher classes we refer to them as child derived or subclass.
Different people use different terms to refer to the same concept. So let's see this in action. We start by creating the
person class. Here we create a constructor with two parameters. first name of type string and last name
of type string. Now here we need a getter called full name for combining the first and
last names. So get full name and here we return this first name plus a space and this dot last name. Now
here we can have other methods like walk and talk. So let's implement one more method walking. Okay, so here's our
person class. Now let's create the student class. So right after the person, we create another class called
student. And here we use the extends keyword to tell the student class inherit everything from the person
class. Now a student can have additional properties and methods. For example, every student can have a student ID. So
let's create a constructor and give it a parameter public student ID of type number. Now here we have a compilation
error saying constructors of derived classes must contain a super call. So in Typescript we have a special keyword
called super that we use to reference the base class. So here we type super followed by parenthesis to call the
constructor of the base class. Now here we need a first and last name. And that means we need to add these
parameters in this constructor as well. So public first name of type string and public last name of type string. Now
earlier I told you that when we use an access modifier like public or private here we're essentially creating a
parameter property. So the TypeScript compiler will create a property by this name and initialize it for us. Now in
this case we don't want to use the public modifier here because we already created this
properties in the person class over here. Okay. So let's simplify our code and remove the public modifier. We only
need it for the student ID which is a property specific to this class. Okay. Now here we call super and pass first
and last names. Good. Now in this class we can have additional methods like take test and
here we just log taking a test. Okay. So this is inheritance. Now let's create a student
object. So let's create a student object. New student. And here we pass one for the student ID and John and
johngmail.com. Now let's type student period. Look, we have inherited first name, full name and last name from the
base class. And we also have student ID that we implemented in the student class. And similarly, we inherited the
walk method from the base class. But we also implemented an additional method in the student class. This is the benefit
of using inheritance. Now, one last thing before we finish this lesson. In this example, I implemented both these
classes in the same file. But as a best practice, we should implement each class in a separate file. We're not going to
do that yet because first we have to talk about modules so you understand how modules work before you can move your
classes to separate files. [Music] So inheritance is great, but sometimes
we want to change something in the inherited code. For example, look at the full name getter in the person class.
Let's say we're going to implement the teacher class. And when getting the full name of a teacher, we want to prefix
their name with professor. So in the teacher class, we want to change the implementation of the full name getter.
This is called method overwriting. So overwriting a method means changing its implementation. Let me show you how to
do this. So down the bottom, we don't need the student object. Instead, we're going to create the teacher class. So
class teacher extends person. Now at this point, I don't want to add any extra properties in this class. So we
don't need to create a constructor. And that means we're going to inherit the constructor of the person class. So
right here we can create a teacher new teacher and now we see the constructor of the person class that we
have inherited. So let's pass some values like John Smith. Now we do a console.log and print
teacher.name. Now if you run our program obviously we see John Smith. But we want to change the implementation of the full
name getter and prefix the name of the teacher with professor. So back to the person class
to save time. I'm going to copy the full name getter and then paste it in the teacher class. Now here we need to
prefix this with the override keyword to tell the compiler that we're overriding or changing the implementation of this
method. Now over here first we're going to add professor followed by the first and last
names but we already implemented this piece of logic in our base class. So we don't want to repeat that here. This is
where we use the super keyword once again. So let's remove this expression. We type super to reference the base
class. So super dot full name. Okay. Now let's run our program one more time. Now we see Professor John Smith.
So this is how we can overwrite methods in Typescript. Now one more thing before we finish this lesson. In this case, we
could still achieve the same result without using the overwrite keyword. So if we run our program one more time, we
still see professor John Smith. There is a tiny problem though. The problem is that this method, this getter is now
disconnected from the one defined in the base class and this can cause issues as you will see in the next lesson. So as a
best practice, we're going to enable a special compiler option that would remind us to use the overwrite keyword.
So let's go to tsconfig.json. In the type checking section, we have a compiler option
called no implicit override. So we're not going to allow implicit overriding of methods. We have to be
explicit about that. So now back to our teacher class, we have a compilation error saying this member must have an
overwrite modifier because it overrides a member in the base class person. So the compiler knows that we have a getter
by the same name in the person class and that's why it's complaining and saying, "Hey, you forgot to use the overwrite
keyword." So now we add that and the error goes away. Next we're going to talk about a powerful technique built on
top of method overriding called [Music] polymorphism. One of the core principles
of object-oriented programming is polymorphism. Poly means many, morph means form. So polymorphism means many
forms and this refers to the situation where an object can take many different forms. Let's see this in action. So
here's our teacher class right after let's define a function for printing the name of some people. So print names
takes a parameter called people of type person array. Now in this function we're going
to use a for loop to iterate over our person array. So for let person of people, we're just going to log person
full name. Now let's call this function and give it an array of person
objects. Now you know that every student is a person. The same is true for every teacher. So in this array we can add
student or teacher objects. So here we can add a student object and initialize it with one John Smith and a teacher
object Mosh Hammedai. Okay. Now let's run our program. So we see different output
depending on the type of person. If you have a student, we see their name. If you have a teacher, we see their name
prefixed with professor. Now I'm not a professor and I don't ever want to be. So, excuse me for that. But back to our
code. What you see here is polymorphism in action. And this is extremely powerful as you will see in a minute.
Why is this called polymorphism? Well, in this for loop, let's hover our mouse over the person object. This object is
of type person. But in each iteration of the for loop, this person object is taking a different form. In the first
iteration, it takes the form of a student because the first element in this array is a student object. In the
second iteration, our person object takes the form of a teacher object. And that is why the full name property or
the full name getter gives us different outputs. So our person object is taking many different forms and is acting
polymorphically. Now why is this powerful? Well, because tomorrow we can create a new class like principal which
represents a school principal and print its name without making a single change to this function. Let me show you what I
mean. So here we're going to define a new class called principle which also
extends the person class. Now here we're going to override the full name getter and instead of professor we're going to
print principal. Okay. Now because a principal is also a person, we can add a principal object in this array.
Principal is going to be Mary Smith. Okay. Now when we run our program, we see a different kind of output on this
line. So what is happening here is that we have enhanced our program without a single change to this function. So we
have implemented some new functionality just by writing new code. And this brings us to another principle of
object-oriented programming called openclosed principle. This principle says our classes should be open to
extension and closed for modification. So we should be able to extend them or inherit from them but we should not
modify them because if you modify or change a class then potentially we might break something somewhere else and end
up with a bug. If we properly test the class and know it's working then we don't have to touch it. So we can
enhance our application simply by adding new classes. So we write new code instead of changing existing code. This
is the idea behind the open close principle. Now practically speaking we cannot adhere to this principle 100% of
the time. It's impossible and even costly. This is just a guiding principle. What I want you to take away
from this lesson is that polymorphism allows us to follow this guideline. Again there is way more to this. If you
want to see more examples of polymorphism and open close principle, you really need to look at my design
patterns course. Now, one last thing I want to mention before we finish this lesson. In the previous lesson, I told
you that we have to use the overwrite keyword to tell the compiler that here we are overriding or changing the
implementation of the full name getter. And this is how we achieve polymorphic behavior in this method. So the full
name getter is defined in the person class but we have changed its implementation in a couple of subasses.
Now if we didn't use the override keyword here this getter would be disconnected from the one defined in the
base class. So someone could go and make a change there. Perhaps they could refactor that. They can change the name
or restructure it and this would break our polymorphic behavior. So that's it for polymorphism. Next we're going to
talk about private versus protected members. [Music]
So far you have learned about public and private members. But in Typescript we also have protected members. Let's talk
about the differences. So here in the personal class you know that if we make the walk method private we can access it
anywhere within the class but we cannot access it from the outside. So if we create a person object, we cannot call
the walk method on that object. Now protected members are exactly the same. So we can access them anywhere within
the class but not from the outside. The difference is that protected members are inherited but private members are not.
So the walk method is protected and that means we can go to a child class like this student and access it. So if we
type this.walk, it's right here. But if we make the walk method private now this method is not
inherited. It's only available in the person class. So here in the student if we type this dot we don't see the walk
method. This is the difference between private and protected members. Now protected members is something that you
don't or you shouldn't use that often unless you know what you're doing because they can create coupling in your
applications. So just stick to public and private. I only talked about protected members here in case you see
them in other people's code. So don't use them unless you really know what you're
[Music] doing. Let's say we're building an application and we want to allow the
user to draw something on a canvas. So here we're going to have classes like circle, rectangle and so on. Now all
these classes should have some common properties like color, size, position and so on. So let's define these common
properties and methods in a shape class. So we define a new class called shape and give it a constructor with one
parameter for now called color of type string. Now here we should also have a method called render for rendering the
shape. Now, here's the thing. At this point, we don't know how to render a shape because the rendering algorithm
really depends on the type of the shape. So, here we have to create another class like circle that extends the shape
class. Now, here we need a constructor. Let's add two parameters here. Every circle needs a radius. So,
public radius of type number. We also need color to pass to the base class. But note that here I didn't add the
public keyword because we already define this property in the base class. Okay. So in this constructor we simply use the
super keyword to call the constructor of the base class and pass the color. Good. Now in the circle class we simply type
render and press enter. And VS Code automatically creates this function signature for us. So note that it
automatically added the overwrite keyword. So we don't have to remember to add it. Okay. Now here we can simply log
and say rendering a circle. So the actual algorithm for drawing a circle is implemented here. Now similarly we could
have other classes like rectangle, triangle and so on. And in all these classes we implement the render method.
Okay. Now there is a problem in this implementation. Let's look at that problem. So with our current
implementation, we can create a shape object with the red color and call its render method. Now why is this a
problem? Well, what does it mean to render a shape? This doesn't really make sense. We shouldn't be able to render a
shape because shape is not a real thing like a circle. This is where we use abstract classes and methods. So if you
want to stop us from being able to create an instance of the shape class, we mark this class as abstract. So on
the top before the class keyword, we type abstract. And with this, we're telling the TypeScript compiler that
this class is abstract or simple or not ready. So another class like circle has to extend it. Okay. Now with this if we
try to create an instance of a shape we get a compilation error saying cannot create an instance of an abstract class.
Okay. So an abstract class is like an uncooked meal. It's not ready. Another class has to extend it. Now quite often
in abstract classes we have abstract methods and these are methods that have no implementation. There's really no way
for us to implement them just like the render method here. So here we prefix the render method with abstract as well.
Now we get a compilation error saying method render cannot have an implementation because it's marked
abstract. So we have to remove the braces and terminate this line with a semicolon. Now we still have another
error saying render which lacks return type annotation implicitly as an any return type. So here we need to annotate
the return type which should be void. Okay. So now we have an abstract class with an abstract method. Remember that
abstract methods can only exist inside abstract classes. So if you remove the abstract keyword from
here, we get a compilation error saying abstract methods can only appear within an abstract class. Okay, so this is all
about abstract classes and methods. [Music] So you have learned a lot about classes.
Now in object- oriented programming, we have another building block called an interface. Now as the name implies, we
use interfaces to define the interface or shape of an object. For example, let's say we want to represent a
calendar concept. Now you know that we have different calendar implementations. For example, we have Google calendar, we
have iical from Apple, we have Outlook calendar and so on. Now, all these calendars should have some common
properties and methods. So, we can define all these commonalities in a base class called calendar. So, we define a
class called calendar. Give it a constructor with one parameter name of type
string. Now, here we can have methods like add event which should take an event object. But let's not worry about
that event object and just add this method here. Similarly, we can have a method
called remove event that takes an event object. Now, none of these methods can have implementation because the
implementation really depends on the type of calendar. For example, how we record an event in a Google calendar is
certainly different from an outlook calendar, right? So, technically we should declare all these methods and
this class as abstract. So, first we make the class abstract. Then we make these methods abstract. Here we should
remove the body and just add the return type annotation. So void and one more time
abstract void. Good. So now that we have an abstract class, we can create concrete implementations like Google
calendar, Outlook calendar and so on. But before we implement any new classes, let's compile our code and see what we
get in JavaScript. So here in the terminal, let's run tsc. Good. And then go to
index.js. So we have the calendar class with a constructor. Now, as you can see, we don't have the abstract keyword here
because abstract has no representation in JavaScript. It's purely a TypeScript concept. Okay. Now, this class is
totally fine. But let's see what happens if we define the same concept using an interface. So back over here I told you
that view is an interface to describe the interface or shape of an object. So using an interface we can say all our
calendars should have a name property as well as these two methods. So for comparison I'm going to comment out
these few lines and redefine the same concept using an interface. So we start with the interface keyword then give our
interface a name like calendar. Now, people coming from C background, they like to prefix their interface names
with a capital I. Some people don't like this convention. If you're one of them, don't use it. Don't waste your time
arguing which convention is better. Should we prefix our interfaces with a capital I or not? There's really no
point arguing about these things. So, here's the calendar interface. Now, here we should describe the shape of every
calendar object. So, in every calendar, we want to have a name property of type string.
as well as these two methods add event which returns void and remove event which returns void as well. Now
immediately you can see that our new implementation is more concise. It's shorter. We don't have all these
abstract keywords but let's recompile our code and see what we get in JavaScript. So one more time tsc and
then we go to index.js. Look, there is nothing here because in JavaScript we don't have interfaces. So
this interface that we define over here is purely used by the TypeScript compiler for type checking. Now here's a
question. Shall we use an abstract class or an interface? Well, it depends. In this example, our calendar class is not
providing any logic or any algorithm that subasses can reuse. We just have a bunch of method declarations. So in this
case it's better to use an interface because our code will end up being more concise and shorter both in Typescript
and in JavaScript. Right? Now in contrast if we had some logic here some algorithm some method with a few lines
of code and we wanted to share that code amongst subasses then we couldn't use an interface because interfaces cannot have
method implementations. So if you add braces here we get an error. We only have method declarations. we only
specify the signature of our method. Okay. So here we have an interface. Now similar to classes here we can use
inheritance. So we can define another interface called cloud calendar that extends the calendar interface. So it's
going to inherit all these members and add something extra. So here we can have a method like sync for synchronizing to
the cloud. Okay. So now we have an interface. Then what? Well, at some point we need concrete or real calendar
implementations like Google calendar. So we define a new class like Google calendar. Now instead of using the
extends keyword, we use the implements keyword followed by an interface like calendar. Okay. Now here we have a
compilation error saying class Google calendar incorrectly implements interface calendar. type Google calendar
is missing the following properties from type calendar name add event and remove event. So the compiler is saying hey you
haven't implemented this interface. Now let me show you a trick here. We put the cursor on the class name. If you're on
Mac press command and period if you're on Windows press control and period. Now here we select implement interface
calendar. So VS Code automatically generates this code for us. Now here we have a compilation error for name
because we haven't initialized it. Now unfortunately at the time of recording this we cannot convert this to a
constructor parameter using a shortcut. So we have to manually create that constructor with one parameter public
name of type string and now we remove this property. Okay. So now we have a class that implement the calendar
interface. Tomorrow we can create another class like outlook calendar that implements the same interface. Now both
these classes will end up having the same shape. So using an interface we can describe the shape of an object. That's
it about interfaces for now. We'll talk about them again later in the course. So that brings us to another section. In
the next section we're going to talk about generics. Welcome back to another
section of the ultimate TypeScript course. In this section, we're going to talk about creating generic and reusable
types. We'll talk about generic classes, functions, interfaces, constraints, and a powerful technique for transforming
types called type mapping. So, let's jump in and get [Music]
started. Before we talk about generics, let's talk about the problem they try to solve. So let's say we want to represent
a key value pair. So we define a class called key value pair and give it a constructor with two parameters. Key of
type number and value of type string. And now we can create an object with these values one and apple. Now what if
somewhere else in our application we wanted to use a string value for the key? With our current implementation, we
cannot do this. We get a compilation error. So here we have two solutions. One solution is to use any here. Now we
can pass any kind of object for the key. But as I told you before, we shouldn't use any as much as possible because with
any we lose type safety. So if you type pair key dot look, we don't see anything about the key. So we don't have type
checking. We don't have intellisense. we don't see the properties and methods available in this key object. In
contrast, if we had string here and typed pair key dot, now we can see all the properties and methods of
the key. Okay, so we don't want to use any. So let's revert this back to number.
Now the other solution is to duplicate this class and call the second one string key value pair where key is a
string and now we can perfectly use it right here. Right now the problem with this implementation is that it's
redundant. What if somewhere else in our application we want to use a person object as opposed to a string for the
value? Then again we'll have to create yet another kind of key value pair class. And there is really no end to
this. You have to constantly create new classes. So we need a generic meaning a common and reusable solution. We'll talk
about that [Music] next. All right. Now let's see how we
can use a generic class to solve this problem. So right after the class name we type angel brackets and in between
the brackets we type one or more generic type parameters. We can call this parameter t or anything we want but t is
a very common name because the idea comes from C++. In C++ we refer to these classes as template classes. So generic
classes in Typescript are the same as template classes in C++ and T is short for template. Okay. So we defined a
generic type parameter. Now we go in the constructor and change the type of key from number to T. And then when we
create a key value pair object once again we type angle brackets. Look we have a generic type parameter. So now we
need to supply a value or an argument for this parameter. Now if we type number and then open parenthesis look we
can see key is a number. In contrast if we use string now we can see key as a string. So let's apply a couple of
values like one and a now if we type pair key dot look we can see all the properties and methods
of string objects. So we get type safety and intellisense and we haven't duplicated our code. We have one generic
and reusable class. But we're not done yet. In this example, we have only made the key generic, but we can also make
the value generic as well. So, we add a second generic type parameter. We can call that one U or anything we want.
Another way to name these is T key and T value or we can just use K and V. There's really no right or wrong.
Different people like different naming conventions. So, I'm going to go with K and V because they're pretty short and
self-explanatory. So, now we change the type of key to K and value to V. Now, when creating a key value pair object,
we need to supply two generic type arguments. So, we can use string or number or person or any kind of object
for the value. This is the beauty of generics. Now, what if we don't supply the generic type arguments here? The
compiler can infer the type of the key and the value. So because we have passed two strings here, the compiler knows
that both key and value are string objects. So most of the time we don't have to explicitly supply generic type
arguments. The compiler can infer them for us. So this was generic classes. Next we're going to talk about generic
functions. [Music] Now just like generic classes we can
also create generic functions or methods. So let's define a function called wrap in array. We give it a value
of type number and in this function we simply wrap that value inside an array. Pretty simple. Now we call this function
give it a number and get an array of numbers. Now with this implementation we cannot pass a string value and this is
where we can use generics to make this function generic or reusable. So right after the function name we type a
generic type parameter like t and then use that as the type of value. Now if we pass a string we get a string array. If
we pass a number we get a number array. Very useful. Now this function could also be a method inside a class. So
let's define a class called array utils. Then remove this function inside this class. Now here we have to remove
the function keyword because we use that only for defining standalone functions. So inside classes we don't use this
keyword. Okay. So inside classes we don't use the function keyword. Okay. Now we can create a utils object new
array utils and here we call utils dot wrap in array or we could make this static and
you know that static methods belong to classes. So with this we don't even have to create the utils object. We can
directly say array utils.rap in array. So this was generic functions. Next we're going to talk
about generic [Music] interfaces. In Typescript we can also
make our interfaces generic. So let's imagine we have a website hosted at myebsite.com and here we have an
endpoint for getting the list of users. Now similarly we can have another endpoint for getting the list of
products. Now let's define an interface for representing the result of calling one of this API endpoints. So we call
that result and give it a property called data. Now we don't want the data to be a user or a product because this
is not reusable. So this is where we use generics. So we add a generic type parameter here and then use that as the
type of data. Now, if you want to expand on this, we also need an error here because
sometimes when calling an API endpoint, we might get an error. So, let's add an error property. And the type of this
should be string or null because we may not necessarily get an error. Now, more accurately, the type of data should also
be T or null because if we have an error, then we don't have data, right? So, now that we have a generic
interface, let's see how we can use it. So we're going to define a function called fetch for calling an API
endpoint. We give it a URL of type string and we say this function should return a result of t. Now because this
function is returning a generic result, we should also add the generic type parameter right here. Okay. Now for
simplicity, I'm just going to return an object with data and error set to null. We don't care about actually calling an
API endpoint. Okay. Now let's define two more interfaces. So one interface is going to be user.
Let's say each user is going to have a username. Username of type string. And we should also define an interface for
our products. Let's say each product is going to have a title of type string. Now let's see how we can use the fetch
function along with these interfaces. So we call fetch. Now if we don't type a generic type argument and simply open
parenthesis, look, we get result of unknown. So the TypeScript compiler cannot infer the generic type argument
for us. We have to explicitly specify it. So we say we're going to fetch a user. And
now look when we call this function we get a result of user. This is how we read this expression result of user. So
let's pass some kind of URL. Now we get a result of user. So if you type result data we can see that
data is a user object. So if we type result data dot we can see all properties of our user object. In
contrast, if we fetch a product object, now data is going to be a product. So we see the title property. So this is how
we can use generic interfaces. Next we're going to talk about generic [Music]
constraints. Sometimes we need to constrain generic type arguments. So let's define a simple generic function
that takes a value of type t and simply returns that value. Now we can call this function and give it any kind of value.
We can give it a number, a string and so on. There are no limitations. But what if we want to constrain or limit the
type of objects that we can pass here. Well, let's look at a few examples. So right after our generic type parameter
we type extends and then we specify the type that can be assigned to t. So if we say number or string we can only pass
number or string values here. If we pass a boolean we get a compilation error. That
was one example. We can also constrain by the shape of an object. So instead of number or string we can pass a shape
object and say here we need a property called name of type string. Now we can only pass objects that confirm to this
shape. So here we have to pass an object with a name property. Okay. We can also constrain by an interface. So we can
define an interface called person that has a name property and then we can say t extends
person. That's another example. We can also constrain by class. So let's change this interface to class. And here we
replace this with a constructor with one parameter. Now we can pass an instance of person or
any classes that derive from person. So let's create another class called customer that extends person. Now we
don't want to add anything extra here. What I want to point out is that when calling echo, we can pass a new person
object or a new customer object. So literally any object that is a person that means an instance of person or any
classes that directly or indirectly derives from person. Next we're going to talk about extending generic
[Music] classes. Let's talk about generic classes and inheritance. So let's say
we're building an e-commerce application. So here we can have objects like products, categories, shopping
carts and so on. So let's start by defining an interface called product. And here we add two properties. Name of
type string and price of type number. Now let's say we need a mechanism for storing these objects. So
I'm going to define a class called store. Now we want to be able to store different kinds of objects, products,
orders, shopping carts, and so on. So here we need to add a generic type parameter and make this class generic.
Now in this class we can have methods like add, remove and so on. But first we need an array for storing this object.
So let's add a property called objects of type t array. Okay. Now here we have a compilation error because we haven't
initialized this property. But in this case, I don't want to create a constructor because it doesn't really
make sense to create a new instance of this class and give it an empty array. This is just unnecessary. It's better to
give this responsibility to this class itself. So I'm going to initialize this directly right here. Good. Now let's add
a new method like add which takes an object of type T and returns void. Now in this method we say this object.push
push object. Okay. Now, in our current implementation, we can access the objects property from the outside, but
we shouldn't be able to because we can accidentally wipe out this array. So, we can create a store object new
store of product and then we can accidentally set this object to an empty array. Okay. So,
we need to make this private so it's only accessible within the store class. So, let's make this private and rename
it to underline objects. Good. Now, we cannot do something crazy like this. Okay. So, we have a generic class. Now,
let's see how we can extend this class. I'm going to take you through three different scenarios. First, we're going
to create a new class called compressible store. So in this store, whatever we add ends up getting
compressed. So here we're going to extend the store class which is generic. But look, we have a compilation error
saying cannot find name T. So the compiler doesn't know what T represents here. So if we try to create a new
instance of this class new compressible store look currently we don't have a chance to pass a generic type argument
like product. So what we need to do here is to add a generic type parameter here as well. Now the compiler knows that
whatever we pass for t like here is going to be used as the argument for this generic class. Okay. Now in this
class let's add a simple method like compress. Okay, so now we have a compressible
store and in this store we have two methods add that takes a product object and
compress that we implemented in this new class. So in this scenario we are passing on the generic type parameter.
Okay, so the generic type parameter that we have in the base class is also going to be used in the chai class. Okay, now
let's look at another scenario. So let's define in another class called searchable store of type T that extends
store of type T. Now in this class we want to implement a method for finding objects. So we have find that takes a
name parameter of type string and returns T or undefined in case we cannot find the given object. Now here we type
return this dotobject but this property is not available because we made it private. So earlier I told you that
private members are not inherited in child classes. So to fix this problem we need to go on the top and make this
property protected. Now back over here we can access this underline object. Now here
we call the find method. Now the find method needs a predicate which is an arrow function that takes an object and
returns a boolean value. So we can take an object and then say object name should
equal to the name argument that we receive in this method. Now here we have a compilation error saying property name
does not exist on type t. So the compiler doesn't know that this type has a property by this name. Now here we can
solve this problem by applying a constraint. So over here we can say tx extends an object with this shape name
string. So now we can use this generic class for finding any kind of object that has a name property. It doesn't
have to be product. It could be anything. As long as we have a name property in that object, we can use this
class to store that object and find it. Okay. So in the second scenario, we are restricting the generic type parameter.
Now the last scenario, let's say there are certain operations that can only be performed on products. So here we define
a new class called product store that extends store of product. So in this scenario, we don't have a generic type
parameter here because we're dealing with a very specific store. I don't want this to be generic and for this reason I
fixed or terminated the generic type parameter. So in this scenario we are fixing or terminating the generic type
parameter. Now in this class we can add a method like filter by category. So this
operation is very specific to products. We don't want to use that in a user store or in a shopping cart store.
Right? So here we add a parameter called category of type string and return a product array. Now for simplicity, I'm
just going to return an empty array. So here's what you need to take away. When extending a generic class, we have three
options. We can fix the generic type parameter. We can restrict it like in this case. We can apply a constraint or
we can simply pass it on to the child class. [Music]
Let's talk about the key of operator in Typescript. So we're continuing with the example from the previous lesson. We
have the product interface and the store class. Now let's implement a find method in this class. So we add find and give
it two parameters property and value. With this we should be able to find any object that has a property with this
value. Right? Now let's add the annotations. What is the type of property? It's string. What about the
value? The value can be anything. But here we don't want to use any because that's a bad practice. Instead we're
going to use unknown. So we pass these values and we either get an instance of T or undefined if there is no object
that satisfies this criteria. Now in this method we're going to use the same technique we used in the
previous lesson. So we're going to return this objects.find here we pass a predicate
which is an arrow function. So we take an object and check to see if object of property equals
value. Now here we have a compilation error. Don't worry about it. We're going to get back here in a minute. First
let's see how we can use this implementation. So we create a store object new store of
product. Now let's add an object here. So we pass a product with these values name and price. Okay. Now we can call
the find method and pass these values name and a or price and one. Right? Now there's a
hidden problem here with this implementation. We can call the find method and pass a non-existing property.
And when we run our program, our program is going to crash because there is no product by this name in the product
interface. Right? So this is where we use the Q of operator to solve this problem. Now back to the arrow. What is
the compiler saying? The compiler is saying no index signature with a parameter of type string was found on
type unknown. So because we're using the square bracket syntax, the compiler thinks we're using an index signature
property. So earlier I told you that we use index signatures for dynamically adding properties to an object. But in
this case, we're not dealing with dynamic properties. We're dealing with actual properties of an object. So let's
look at the error one more time. The error is saying no index signature with a parameter of type string was found on
type unknown. So to solve this problem, we should tell the compiler, no, we are not using an index signature. We are
working with actual properties of type T. And to do that, we just change the type of property from string to key of
or property of T. So the property that we pass here can only be one of the keys or properties of type T.
So if t is product then key of t is going to return name or price. So the key of operator returns a union of
properties of the given type. And that is why we can only pass either name or price when calling this method. So if
you pass a non-existing property, we're going to get a compile time error. So using the key of operator, we can catch
this kind of issue at compile time. [Music] Sometimes we need to base a type on
another type. This is called type mapping. For example, here we have the product interface with two properties.
Now what if somewhere else in our application we need a product with read only properties? Well, one option is to
duplicate this interface. Call the second one read only product and make each property read only. But this is
very repetitive and also every time we add a new property in the product interface, we have to remember to add
that property in the second interface as well. Not a good solution. Let me show you a better way. This is where we use
type mapping. So we're going to create a new type based on an existing type. But in this new type, we want to add all
these properties dynamically and make them read only. So we start with the type keyword because here we have to
create a type alias. So we cannot use an interface. We call this read only product. Now here we're going to use two
tools you're familiar with. One of them is index signature. The other is the key of operator. So instead of hard- coding
these property names here, we're going to use the index signature syntax to dynamically add properties and using the
key of operator, we're going to dynamically get the properties of the product type. So I'm going to write an
expression like this. We add square brackets, then we type property in key of product. So using the key of
operator, we're getting all the keys or properties of the product. Now using the in operator we're iterating over these
keys and property in each iteration is going to hold one of these property names. So in the first iteration is
going to be name in the second iteration is going to be price. So this is kind of like a for loop. Okay. So we get these
properties. Now what about the type of these properties? We want to use the same type we have used in the product
interface. Right? So here we type product of property. So if property is name, product of property will return
string. If property is price, product of property will return number. Okay. Now here we can rename property to P if you
prefer shorter code or we can change it to K so it's consistent with key off. So we're getting all the keys of a product
as well as their type. Okay. Now we can make all of them read only in one go. This is what we call type mapping. So
now if we create a product of this type, all its properties are going to be read only. So if we create a product of type
readonly product, let's set its properties name and
price. Now we cannot change any of these properties. So if we set name to some other value, we get a compilation error.
Okay. Now let's take this to the next level. Why do we limit oursel to product? What if we need another type of
readonly object like read only customer? So here we can use a generic type. So we're going to remove product and add a
generic type parameter and then replace all product instances with T. So T and T. As simple as that. Now we can create
a readonly product or a read only customer or any kind of object. very useful. Now using the same technique we
can create a product type with optional properties. So all properties are going to be optional. So once again we start
with the type keyword. We call this optional of T. And here once again we get all the
keys of T. So K in key of T. Now to make each property optional we append a question mark at the end. And for the
type once again we use T of K. Similarly we can create another type and make each property nullable. So let's call this
nullable. And over here we're going to change the type to T of K or null. So the possibilities are endless. Now
because these types are pretty useful, they're actually built into Typescript. So if you Google TypeScript utility
types, you'll find this page on typescriptlang.org. org. On this page, you can see all the utility types that
are available to you. So partial is like our optional. With this, we can make all properties optional. We also have
required where each property is required. We also have read only and so on. Now there are quite a few types here
and there is really no point for me to go over all of them because this page is perfectly documented and you can see
various examples. So spend a few minutes and go over this page to see what types are available to you. And that brings us
to the end of this section. In the next section, we're going to talk about decorators. Welcome back to another
section of the ultimate TypeScript course. In this section, we're going to talk about decorators which allow us to
change and enhance our classes. So we'll start by quick introduction to decorators and how they work. Then we'll
look at creating various types of decorators such as class decorators, method decorators, property decorators,
and so on. So let's jump in and get [Music] started. So what are decorators? Well,
decorators are attributes that we apply to our classes and their members. And with this, we can change how they
behave. They're frequently used in Angular and Vue applications. For example, you might have a class called
profile component. Now, by applying the component decorator, we can convert this class to a component for a web
application. Now, here we have a compilation error because TypeScript doesn't have this component or any
built-in decorators. So, we have to create our own. And that's why these frameworks like Angular and Vue, they
come with a bunch of built-in decorators like component, pipe, and so on. Now, under the hood, this decorator is just a
function that gets called by the JavaScript runtime. So, the JavaScript runtime or the JavaScript engine that
executes our code is going to call that function and pass our class to it. Now, in that function, we have a chance to
modify this class. So we can add new properties, we can add new methods or we can change the implementation of
existing methods. I'm going to show you various examples in this section. Now before we can create any decorators,
first we have to enable a special compiler option because decorators are an experimental feature and their
standards and implementation might change in the future. So let's go to our TSconig file. Here in the language and
environment section, we have a setting called experimental decorators. Let's turn that on. Good. In the next lesson,
I'm going to show you how to create your first [Music]
decorator. All right, let's see how we can create a decorator that we can apply to our classes. So I told you that a
decorator is just a function that gets called by the JavaScript runtime. So let's define a function called
component. And once again note that here we're using Pascal naming convention. So the first letter of every word should be
uppercase. Now depending on where we're going to apply this decorator, the number and type of parameters varies. So
if we're going to apply this on a class here, we should have a single parameter that represents our constructor
function. We can call that anything but it's better to call it constructor. What matters is the type. If the type is a
function, the runtime assumes that we're going to apply this on a class and this represents our constructor function.
Okay. Now in this function we have a chance to modify or enhance our class. So we can add new properties and
methods. We can change existing methods. We can even delete properties and methods. There are no limitations. So
first let's log something on the console and say component decorator called. Now here we can take
our constructor and go to its prototype. Now if you're not familiar with prototypes, I've covered them in detail
in the second part of my ultimate JavaScript series. But practically speaking, every object in JavaScript has
a prototype from which it inherits various properties and methods. So we can go to this prototype and add new
properties and methods. then all instances of profile component or any classes that has the component decorator
will inherit those new properties and methods. Okay. So let's add a new property like unique id and we set it to
date. Now we can also add a method called insert in DOM. So let's say every component should have a method for
inserting it in the DOM or document object model. So when we add a component in the DOM then the browser is going to
show that component to the user. Okay. So here we define a function and simply log inserting the
component in the DOM. Okay. So now every instance of profile component is going to have these new members. Now we could
also solve this problem using inheritance. So instead of defining a decorator, we could create a base class
like component. Here we could define insert in DOM and then we could have our profile component
extend the base component class. This is another way to solve the same problem. But decorators are just another tool in
our toolbox. Okay, so let's revert this back and bring back our decorator. Okay, now let's run
our program and see what we get. So let's compile and run index.js. All right, look, we got this
message even though we didn't create any instances of the profile component. So whether we have zero or 10 instances,
this decorator is called once and this is our chance to enhance or modify our classes. Now let's look at the generated
JavaScript code. So in our project in the disc folder, let's look at index.js. So on the top we have a
function that is automatically generated for us. It's the decorate function. And this is where all the magic happens. So
right after that we have our component decorator. Next we have our profile component and finally we have a call to
the decorate function with our decorator and class as arguments. So this is how the runtime calls our component
decorator to enhance our profile component. [Music]
Sometimes we need to pass arguments to our decorators. So let's see how we can create parameterized decorators. First
I'm going to temporarily comment out our decorator function and redefine it. So function component. Now in this example,
we're passing a number. So let's add a parameter called value of type number. Now in this function all we have to do
is return a decorator function. So let's move this code right here. Remove the comment and add the return keyword. Now
this function doesn't need a name. So let's remove component. We can also improve this code by using the arrow
function syntax. It makes the code slightly more readable. So remove the function keyword and add a fat arrow
right here. That's all we have to do. So now we have what we call a decorator factory. So this function is acting as a
factory for creating a decorator. Okay. So we take a value. Now we can use it anywhere in this function. So we can add
a new property here. Constructor.prototype options. We set it to this value. But let's take this to
the next level and make it more real world like what you see in frameworks like Angular. So instead of passing a
number, let's pass an object with special properties. So first we define a type called component
options. Now here we can have a property like selector which is going to be a string. Now we rename value to options
and change number to component options. And finally, when using our decorator, we pass an
object like this. Selector is going to be the ID of an element in the DOM. So, we can say my profile. Now, if you have
never done front- end development, don't worry about this fancy syntax. All I want you to pay attention to here is
that we're passing an object as an argument to this decorator. [Music]
We can also apply multiple decorators to a class or its members. So let's define another decorator called pipe that takes
a constructor of type function. Now here we're going to log and say pipe decorator called. Then
we're going to take constructor.prototype and add a new property to it called pipe. Pretty
simple. Now let's apply this after the component decorator. So now let's run our program and see how these decorators
are called. So compile and run. All right. Look our decorators are applied in the reverse order. So first the pipe
decorator is called and then the component decorator. The idea behind this comes from the math. So in math if
you have an expression like f of g of x you know that first g of x is evaluated and then the result is passed to f.
Okay. So in this case because each decorator is a function first the pipe decorator is going to get called with
our profile component. So it's going to enhance our component and then the result is going to get passed to the
component decorator. Next we're going to talk about method decorators. [Music]
All right, let's see how we can enhance our methods using decorators. So, let's define a person class with the same
method that takes a message of type string. And here we log person says the message. Good. Now, we're going to
implement a decorator and apply it on this method. So just like before we create a function. Now to apply this on
a method here we need three parameters. So instead of a constructor function we need three different types of
parameters. The first parameter is the object that owns the target method. So target of type any. Now I know that I
told you that we shouldn't use the any type as much as possible but in this case any is the type that the TypeScript
compiler expects from us. Okay, the second parameter is the name of the target method. So, method name of type
string and the third parameter is the descriptor object for the target method. So, descriptor of type property
descriptor. Now, if you're not familiar with property descriptors, I cover them in detail in the second part of my
ultimate JavaScript series. But very briefly, every property in an object has a descriptor object that describes that
property. Okay? And by the way, you don't have to memorize any of these parameters. If you simply Google
TypeScript decorators on this page on TypeScript lang.org, you can see various examples. So on the right, we have class
decorators, method decorators, and so on. So let's look at method decorators. Here we have an example. Look, we have a
function with three parameters. Target of type any, property key of type string, and descriptor of type property
descriptor. So once again, the name of this parameters doesn't matter. What matters is their number and type. Okay,
so back to this function. Now here we can do some really cool things. For example, we can completely replace this
method with an entirely new method. So look this descriptor object has a property called value that references
the target method. So we can set this to a new function to replace the original function or method. So a
function with no parameters and here we simply log new implementation. So now this function is going to replace the
same function or more accurately the same method in the person class. Now this is the nuclear option. Most of the
time we don't want to do this. We want to enhance an existing method. So we want to do something before or after
that method. So let's change the message to before. We're going to do something first. Also we're going to do something
after. And in the middle we're going to call the original method. But before we can do that first we need to get a
reference to the original method. So let's declare a variable called original and set it to descriptor dot value. Now
in this case it's better to use the con keyword because we don't want to accidentally reset this variable to
something else. Okay. So we have the original function. Now how can we call it? Very easy. We type original dot. Now
look we don't get intellisense here because the type of value property is any. So here we need to use type
assertion and tell the TypeScript compiler that this is a function. So right here we type as function. Okay.
Now if we type original dot we can see all the properties and methods of functions in JavaScript. So here we have
a method for calling a function. So we call this and here we need to pass two arguments. The first one is the this
keyword. So this and the second one is the argument or arguments we want to pass to the target function. So look our
same method has one parameter of type string. So over here we can hardcode a value as an argument. We can say blue
sky. So now if we create a person object and call person say hello this value that we pass here is going to get
ignored because it's going to be replaced by this hard-coded value because this is the new implementation
of the same method. So in this new implementation we don't have a parameter like message. We have hard-coded a value
that we're going to pass to the original method. Okay. Now what if you want to use the actual argument that we pass
over here? Well, while redefining the same method, we can add the message parameter message of type string and
then we pass it right here. Okay, so let's run our program and see what we get. But before we do so, first we have
to turn off a special compiler option because here we have a couple of unused parameters and TypeScript compiler is
going to complain about them. So let's go to tsconfig.json. Here in the type checking
section we have a compiler option called no unused parameters. We turn this on earlier in the course but now we have to
temporarily turn it off. Okay. Now here in the terminal let's compile and run our program. So we see before then we
see person says hello and after. Great. So isn't this decorator? We have enhanced our method. However, there is a
tiny problem here. With this implementation, we can only apply this decorator on functions or methods with
this signature. Methods that have one parameter of type string. Not very flexible. So, what can we do here? Well,
to make this more flexible, so we can apply this decorator on any type of method. First, we need to replace string
with any. Then, we rename message to something more generic like orgs. And using the rest operator we allow this
function to take varying number of parameters. So now we can apply this on a function with zero one two or more
parameters. Okay. And also while calling the original function we're going to use the spread operator. So we spread all
these arguments and pass them one by one to the original function. Okay. So this is how we can enhance a method using a
decorator. Now one last thing before we finish this lesson. Note that over here we are using a function expression and
not an arrow function. If we use an error function, we get a compilation error because error functions don't
define their own this keyword. So we cannot use them as methods in a class. So when redefining methods, we should
always use a function expression. [Music] Now let's see how we can create a
decorator to apply on accessors meaning getters and setters. So let's define a person class with a constructor with two
parameters first name and last name. Now in this class we're going to
define a getter called full name. And here we're going to return a template string. Inside the string, we're going
to add this first name and this last name. Now, let me show you a tip. When accessing properties and
methods of an object, you don't have to type their full name like last name. You can use abbreviations. So, ln is enough
to bring up the last name property. Okay, so we have a getter. Now we're going to define a decorator called
capitalize for capitalizing the return value of this getter. So let's define a function called capitalize. Now accessor
decorators are very similar to method decorators because this accessor getter under the hood is just a method. Right?
So what parameters do we need here? Three parameters. target of type any method name of type string and
descriptor of type property descriptor. Now in this function just like before first we need to get a
reference to the original method to the original getter. So constant original. Now previously we used descriptor value
but this doesn't work for getters and setters. It only works for regular methods. So here we have to use the get
property. Now we need to redefine this getter. So descriptor get equals here we use a function expression. Now getters
cannot have any arguments. So here we're not going to have arcs. Okay, we just have a function. In this function first
we call the original getter. So original call and here we pass this. Now note that VS code automatically added this
question mark here. This is called optional chaining. The reason we say this is because original can be
undefined. Look at the type of original. It's either a function with no arguments that returns any or undefined. That's
why we have optional chaining here. So this code is the shortand for code like this. If original is not null and it's
not undefined, then original call this. So this line is just a shortand for these two lines. Okay, delete. Now, in
this case, we know that original is not going to be undefined because we're going to apply this decorator on a
getter. So, original will always have a value. In this case, instead of a question mark, we could use an
exclamation mark. And with this, we're telling the compiler, hey, I know this is not going to be null or undefined, so
trust me. Okay, either way works. So, we call the original getter, get the result, and store it here. Now once
again I'm using the constant keyword here because I don't want to accidentally change the result. Now look
at the type of result it's any. So here we should check to see if result is a string object then we're going to return
string the two uppercase. So if type of result equals string then we're going to return result the two uppercase
otherwise we're going to return the result itself. Now we can simplify this code and use a conditional expression.
So return if this expression is true, we're going to return result two uppercase. Otherwise, we're going to
return the result itself. So now we have this decorator. Let's see everything in action. So let's
create a person object with my first and last name in lower case.
Then we're going to log person full name. Now let's compile and run our program. There you go. So you see my
name in uppercase. Now what if instead of a string we return a number here? Let's
make sure our code still works. So one more time we get zero. Beautiful. Now what if we return null here? Let's make
sure our code still works. So one more time. Okay, we see all beautiful. Next we're going to talk about property
[Music] decorators. All right. Now let's define a decorator for enhancing a property. So
let's define a user class and give it a password property of type string and a constructor for
initializing this property. Now in this case we cannot use the public modifier here and avoid
typing this property because we're going to apply our new decorator on this property itself. So let's bring this
back and remove the public keyword. Good. Now our new decorator is going to be called min length and with this we
can ensure that our passwords are at least let's say four characters long. So let's define a function called min
length. Now here we need a parameterized decorator. So let's give it a parameter called length of type
number. Now here we're going to return the actual decorator function. Now property decorators are very similar to
method decorators. So here we're going to return a function with two parameters target of type any and property name of
type string. So very similar to method decorators but here we don't have a property descriptor. Okay.
Instead we're going to define a property descriptor for the target property. So we declare a variable of type property
descriptor. Now it's actually better to use the const keyword here because we don't want
to accidentally reassign this. Okay. Now in this object if you press control and space we can see all properties and
methods of descriptor objects. So here we have a bunch of optional properties because all of them have a question mark
at the end. Now out of these in this lesson we're going to work with get and set. So here we can define a setter for
the target property and in that setter we can perform data validation. So if the new value is less than let's say
four characters long we can throw an error. So let's define a setter that takes the new value as a string. And
here we can check to see if new value the length is less than the length that we have specified up
here then we're going to throw an error. So throw new error property should be at least four characters long. Now we don't
want to hardcode these values like four. We want to render them dynamically. So instead of using single quotes, we're
going to use back tick to define a template string. Now in a template string, we can have placeholders for
rendering values dynamically. So instead of four, we're going to add dollar sign braces. And this is going to be the
placeholder for the length parameter. Now similarly we're going to render the property name dynamically. So
one more time dollar sign braces property name. Good. So this is our data validation logic. If the value is valid
then we're going to store it somewhere. But where? Well, in this decorator function, we can declare a
variable called value of type string. And if the new value is valid, we can set value to new value. Now, we also
need a getter for returning the value of this variable. So, in this descriptor, let's define a getter. And here we
simply return the value. Okay. So now that we have a descriptor object the last step is to assign it to the target
property. So right after here we call object that define property and here we're going to pass
three values the target object property name and the new descriptor object. Okay now let's see this in action. So let's
create a new user. New user. Now if you pass a password that is at least four
characters long, everything is going to work. So let's log user.p password. So let's compile and run our
program. There you go. Everything is working well. But if you pass a password that is less than four characters
long, we're going to get an error. Similarly, if you pass a valid password initially, but then we reset it to
something invalid, then once again you're going to get an error. So every time we try to
set the password, our decorator gets called and validates the new value. So with property decorators, we can enhance
existing properties. [Music] The last decorator type we're going to
talk about is parameter decorator. Quite frankly, it's not something use that often, but if you're designing a
framework for other people to use, then you might want to know how this parameter decorators work. So, let's
start by defining a vehicle class with a move method that takes the speed as a number. Now we're going to define a
parameter decorator for watching this parameter. So let's define a function called watch with three parameters.
Target of type any, method name of type string, and parameter index of type number. The parameter names are quite
self-explanatory, so there is no need for me to explain them. Now, at this point, there is really not much we can
do with this parameter. Quite often we store some metadata about these parameters so later our framework can do
something about them. Let me show you what I mean. So let's define a new type called watched parameter. There are two
pieces of information we need to know about these watch parameters. The method name and parameter index. Now method
name is going to be a string and parameter index is going to be a number. Okay. Now that we have this new type,
let's define an array of watch parameters. So let or const is better. Watched parameters is of type watched
parameter array. And here we initialize this to an empty array. Now in this decorator function, all we can do right
now is store an object in this array. So watch parameters.push. Here we add an object
with two parameters, method name and parameter index. So we're collecting some metadata
and our framework later on can use this metadata to do something. Perhaps at some point we're going to create objects
and we want to know what parameters we should watch. So let's not worry about all this complexities and just
log the list of watched parameters on the console. All right, take a look.
So now we know that in the move method we have to watch the first parameter. Welcome back to another
section of the ultimate TypeScript course. In this section, I'm going to show you how to use modules to organize
your code. We'll start by discussing how to create and use modules. Then we'll talk about module formats and how to
configure the TypeScript compiler to generate code for different formats. and then we'll explore additional techniques
such as default exports, wildcard imports, and reexporting for writing cleaner and more concise code. So, let's
jump in and get [Music] started. So far, we have written all the
code in just one file. But that's not how we build real applications. Because as our code grows, maintaining these
large files gets harder and harder. So we should split our code into different files or modules, each serving a
purpose, just like how we organize our kitchen wear in different cabinets and containers. So here we have two classes,
circle and square. Now I'm going to move this to a new file or a new module called shapes. So back to our project
here in the source folder, let's add a new file called shapes.ts. Now let's move all this code right here.
Okay. Now these classes are currently only accessible within the shapes module. So we cannot use them anywhere
else unless we export them. So let's export the circle class so we can use it in the index module. Now back in the
index module on the top we type import braces from and here we type the name of the target module as a relative path. So
we type a period and a forward slash to reference the current folder and then we type the name of the target module in
this case shapes. Now note that here we don't type.ts or the extension just the file name. Okay. Now if you want to go
one level up instead of one period we use two periods. Okay. So here we have the shapes module. Now back to the
braces. Now if you press control and space here we can see all the exported objects from this module. In this case,
circle. Now, while importing this, we can optionally rename it to something else like my circle. If you have another
class or another interface or another type in this module with the same name, this way we can prevent name clashes.
And currently, we don't have this scenario. So, this is unnecessary. Okay. Now, let's go back to the shapes module
and also export the square class. So, we can import it right here. So we type a comma followed by the new type. Okay. So
now that we have imported these classes, we can use them just like before. So we can create a circle object with a radius
of one and then log its radius. Now VS code is complaining that we have imported the square class but we have
never used it. So we can put the cursor here. If you're on Mac, press command and period. If you're on Windows, press
control and period. And here we select remove unused declaration for square. With this we can clean up our code. Also
it's nicer to add spaces inside the braces. That makes our code slightly more readable. Okay. Now let me show you
a shortcut. We don't always have to manually import objects on the top. In a lot of cases, VS Code can help us. So
I'm going to delete everything here and just create a circle object. Now note that the moment I type circle and press
enter, VS code automatically added the import statement for us. Beautiful. Now what if we were not typing this, what if
we copied this code from somewhere else. So let me remove this and just create a circle. Imagine we copied this from
somewhere. Well, you can see that here we have a compilation error, right? So once again we can press command and
period or control and period. And here we select add import from shapes. That's a very useful shortcut. Now, let me show
you one more shortcut before we finish this lesson. So, let's define an interface called product with a name
property of type string. Now, if you want to move this to a new file, we can put the cursor on the interface name.
Once again, we press command and period or control and period. And here we select move to a new
file. Now, in our project, we have a file called product.ts. Now note that in this case VS code is using the passcode
convention for naming this file because we have defined a type with this name in this file. Which convention you use
really depends on you. There is no right or wrong. Different teams like different conventions. So that was the basics of
creating and using modules. Next we're going to talk about module [Music]
formats. All right. Now let's talk about module formats. So in the old days, JavaScript didn't have a module system.
So we used various techniques and formats to modularize our applications. If you have been coding in JavaScript
for 10 years or longer, you've probably heard of module formats like AMD, UMD, CommonJS and so on. Now starting with ES
2015 or ES6, modules have become natively supported in JavaScript. So this syntax we have for exporting or
importing objects, this is part of the ES6 modules. Now these days this format is widely supported in pretty much all
browsers and JavaScript runtimes out there. So we don't really need the other formats anymore. But let's spend a
couple of minutes and see this module formats in action so they are not foreign to you. So let's go to our TS
config file. Here in the module section, we have a compiler option called module that is set to common.js. JS by default.
This is the module format that initially came with node. So now let's compile our code and see the generated JavaScript
code. So here in the terminal, let's run tsc. Good. Now let's look at shapes.js in the disc
folder. So look, our code looks different. So our classes don't have the export keyword in front of them.
Instead, we have a statement like this. exports circle equals circle. What is happening here is that exports is an
object that contains everything we're going to export from this module. Now on this line, we're adding a property on
this object called circle and we're setting it to the circle class we defined over here. So this is the common
JS format for modularizing applications. Now in index.js, look, we no longer have an
import statement. Instead, we have a call to the require function. So, we're requiring this module to load it and
then we're referencing shapes.circle to create a circle object. So, this is the common JS format. Now, the great thing
is that you don't have to learn this format if you're using TypeScript. So, you can continue using import and export
statements and the TypeScript compiler will translate your modules into the CommonJS format if that's what you need
in the generated JavaScript code. Now let's see what happens if we change our module format. So back to ts config.
Let's remove common.js and press control and space to see all the available options. So we have AMD, CommonJS, ES
2015, ES2020, ES2022 and so on. Now honestly I'm not sure about the difference between ES2020 and ES 2022.
Most of the time we use ES 2015 because that's widely supported. So let's set that. Now let's recompile our code and
have a look at shapes.js one more time. Take a look. This is the syntax we used for exporting these classes. Right
now let's look at index.js. And over here we have the input statement. So depending on the
module format we set in our TS config file, the generated JavaScript code is going to be different.
[Music] Sometimes we just want to export a single thing from a module. In those
cases, it's better to use a default export. Let me show you what I mean. So here in the source folder, let's add a
new file called storage.ts. In this module, we're going to define a class called store for
permanently storing some objects. So here we can have methods like add, find, delete, and so on. Let's not worry about
those details. Now in the same module, we can have other classes like compressor and
encryptor for compressing and encrypting objects while storing them in the storage. Now in this case I don't want
to export these classes because these are part of the implementation detail of this module. So other modules should not
know about their existence. All they need to know is the store class. So they just create a new store and add an
object to it. Everything else is implementation detail. Let me give you a metaphor. Think of a remote control. A
remote control has a bunch of buttons on the outside and a bunch of other stuff on the inside. You're not supposed to
touch anything on the inside. What is inside is considered the implementation detail and it might change in the
future. What matters is what is on the outside. So in this module if we export these classes other modules might
potentially use them and become dependent on them. Then in the future if we decide to change these classes if we
decide to remove them or restructure them other modules that are using this classes might potentially break. So we
should reduce the surface area of our modules. We want to make our modules like a remote control with very few
buttons. This way we can reduce the coupling or dependency between our modules. Okay. So here we can export the
store class and then go to our index module and import it just like how you learned earlier. Now this is good but if
you're exporting only a single thing from a module there is a better way. So back to the storage module here we type
default to indicate that this is the default object exported from this module. Now while importing it we no
longer need the braces. So that makes our code slightly cleaner. Now default exports can also coexist with named
exports. For example, back to our storage module, let's define an enum called format. And here we can have
members like raw and compressed. Let's imagine that other modules need to know about the format enum. So we're going to
export it just like before. Now while importing this objects here in the index module first we import the default
object and then we add braces and here we import the named exports like the format enum. Okay so this is all about
default [Music] exports. Sometimes we need quite a few
objects from a module. So importing them one by one can be a bit of a hassle. For example, let's import circle and square
from the shapes module. Now here we're importing only two classes. But what if we were importing 10 classes or more? We
would end up with a really long import statement, right? In those cases, we can use a wildcard import to simplify our
code. So we remove the braces and type import everything as shapes. So now we're importing all the exported objects
from this module and putting them inside a bucket or a container called shapes. So if you type shapes dot, we can see
all the exported types. So here we can create a circle like
this. This is what we call a wildcard import. [Music]
All right, let's talk about reexporting. With reexporting, we can have a single module combine the export of different
modules. Let me show you. So here in the source folder, let's add a new folder called shapes. Then we move the shapes
module in this folder. Now look at the shapes module. Currently, we have defined both these classes in the same
module. And there is nothing particularly wrong with this. But as these classes get larger and larger,
this module becomes harder to maintain. In that case, it's better to split this module into a few different modules. So
now let's put the cursor on each class and move it to a new file. Good. And one more
time. Okay. So now in the shapes folder, we have two extra files, circle and square. And the shapes module is empty.
Now let's go to the index module and bring these two classes. So first we import circle and then square. Now
there's nothing significantly wrong here. But what if we had 10 types of shapes that we had to import on the top?
We would end up with 10 import statements, right? This is where we can use reexporting to combine all these
modules into a single module and then we'll import only that single module here. Let me show you.
So back to the shapes folder. First we rename shapes.ts to index.ts. So in this module, we're going
to combine the exports from these other modules. So this is where we import the circle class and the square and then we
export them at the end. So export braces circle and square. Okay. Now with this new structure in index.ts, ts we don't
have to import circle and square separately from two separate modules instead we can import circle and square
from shapes slashindex module that is better right but we can take this to the next level
we don't even have to type the index module so we can simply import them from the shapes folder or sometimes we refer
to this as a package so shapes package now here we have a compilation error saying cannot find module shapes. Did
you mean to set the module resolution option to note or to add an alias to the paths option? To fix this problem, we
have to go to our TS config file here in the module section. The default module resolution setting is classic which is
for backboard compatibility. So, it's a good practice to uncomment this line and set the module resolution to node as
opposed to classic. With this, we no longer get that compilation error and our code is cleaner. Now, there's one
more thing I need to show you here. Back to this index module, we have a shorthand syntax for importing and
exporting these classes in one go. So, we remove the last line and change import to export. So now we import and
export these classes in one go. This is what we call reexporting. Welcome back to another section of the
ultimate TypeScript course. In this section, you will learn how to run TypeScript and JavaScript code side by
side. So, first I will show you how to include JavaScript code in your TypeScript projects. Then we'll talk
about type-checking JavaScript code using JSON and declaration or type definition files. And finally, we'll
talk about using the declaration files from the definitely typed repository. So, let's jump in and get
[Music] started. So far, we have been coding purely in Typescript, but that's not
always possible because you might have a JavaScript project and decide to introduce TypeScript later on or you
just want to work with some existing JavaScript code. So, for this demo, let's add a JavaScript file in the
source folder. We're going to call that tax.js. And here we're going to export a function called calculate tax that takes
on income. And here we return income times.3. So we have a regular JavaScript file. And that means in this file we
cannot use any TypeScript features. So we cannot annotate this parameter with number. Okay. So we have a regular
JavaScript file. Now let's import this function into the index module. So we import calculate tax from the tax
module. Okay. Now we get a compilation error because by default JavaScript code is not included in the compilation
process. So the TypeScript compiler cannot see our tax module. To solve this problem, we have to go to our TS config
file. Here in the section called JavaScript support, we have to turn on this compiler option called allow JS.
Now back to the index module, we still see the error. Don't worry about it. This is just a temporary issue in VS
Code. So here we can call calculate tax, pass some value, get the tax, and then log it on the console. Now let's compile
and run our program. We get a compilation error saying cannot use import statement
outside a module. The reason we see this is because in the last section we changed our module format to ES 2015 and
that is used by browsers. We can use that in node applications as well but that requires a little bit more work. So
to solve this problem we have to go back to tsconfig.json on the
top. We're going to change the module format to common js. Now let's compile and run our
program one more time. Okay, it's working. Beautiful. Next, I'm going to show you how to enable type checking in
JavaScript [Music] code. When using JavaScript code, by
default, we don't get any type checking. So that means we can call this function without supplying an argument and we
will not know about this issue until runtime. So we have to compile and run our program to see some weird result.
Now to solve this issue, we go back to tsconfig.json. to JSON. Here in the JavaScript support
section, we have another compiler option called check.js. If we turn this on, we get some basic type checking. It's not
as comprehensive as what we get in TypeScript files, but it's better than nothing. Now, let's recompile our code
and see what we get. Look in tax.js on line one, we have a compilation error saying parameter income implicitly has
an any type. So we are saying this because earlier in the course we turned on a compiler option for catching this
kind of errors. So we shouldn't use the any type unless we explicitly type it out. Right? So now we have two options
here. One option is to describe type information to the TypeScript compiler. We'll talk about that in the next
lesson. But let's imagine that we're getting a ton of errors in this file and we want to temporarily tell the
TypeScript compiler to stay silent. So let's go to tax.js. On the top we add a special
comment here. We type an at sign followed by ts dash no check. So we add this once on top of a file that we don't
want the TypeScript compiler to check. Now let's recompile and run our program one more time. All right, we don't get a
compilation error, but we still see this weird result. Why is that? So back to the index module. You might be wondering
why the TypeScript compiler is not complaining even though we are not supplying any arguments to this
function. Here's the reason. If we don't supply an argument, undefined will be passed to this function. And because the
type of income parameter is any, it can receive any type of arguments. So, it can receive undefined. That is why we
don't get a compilation error here. In the next lesson, I'm going to show you how to describe type information to the
TypeScript compiler. Soap will be forced to pass a valid argument [Music]
here. So one way to describe type information for our JavaScript code is using JS doc which is a special type of
comment we can add to our code. Let me show you. So first we remove this line. Then we type a forward slash followed by
two asterisks. Now look, VS Code detected this line as a JSON comment. So let's press enter to generate the
comment. Good. So you can see we have one parameter called income and here in braces you can see the type of this
parameter. Now at this point VS code doesn't know the type of this parameter. That's why we have an asterisk here. So
here we need to jump in and explicitly set the type of this parameter to number. Now similarly on the second line
we can specify the type of the return value. So once again we type braces with number. Now let's recompile our code and
see what happens. All right. This time we get a compilation error in index.ts on this
line. There is saying we expected one argument but we got zero. So let's go to index.ts and supply an argument right
here. Good. Now in the previous videos I had a compilation error on this line saying there is no type definition file
for this module. If you get the same error just restart VS code. Okay. Now let's recompile our code one more time.
All right. No more compilation errors. Great. So using JS doc we can provide type information to the TypeScript
compiler. But we can also explain our code. For example on the first line we can provide a description for this
function. We can say calculate income tax. Then similarly we can explain each parameter. For example, here we can say
net salary after expenses. Now back to index.ts. If you hover over this function, we can see the descriptions we
provided. That's very useful. So this is JS doc, but we also have another way to provide type information to the
TypeScript compiler. We'll talk about that next. [Music]
We have another way for providing type information and that is using a declaration or a type definition file.
This is useful if you don't want to modify your JavaScript code and add these comments. So let's delete these
lines. Now back to our project here in the source folder. Let's add a new file called
taxd.ts. So the name of the file should be the same as the corresponding JavaScript file but the extension should
be d.ts. d is short for declaration and this is the same as a header file in C if you have done any C programming. So
in this declaration file we declare all the features of the target module. So our target module is tax.js and here we
have a function called calculate tax. So now we need to declare this in this declaration file. We say declare
function calculate tax with income parameter of type number and this function returns a number and then we
terminate this line using a semicolon. So all we're doing here is declaring this function. The actual implementation
is somewhere else in the target module. Okay. Now if we export this the TypeScript compiler will know that our
calculate tax function expects an income parameter of type number. So back to index.ts if you remove this argument we
get a compilation error saying an argument for income was not provided. Okay. So let's bring this back. So using
declaration files we can provide type information to the TypeScript compiler. Just remember when using this approach
you should describe all the features in the target module. Anything you don't describe will be invisible to the
compiler. For example, if we go to this JavaScript file and export another function, say hello, and then we forget
to declare this in this declaration file, this is going to be invisible to the TypeScript compiler. So if you go to
index.ts, we're not going to be able to import say hello from the tax module. So when using declaration files, you should
describe all the features of the target module. [Music]
All right. Now, let's talk about using thirdparty JavaScript libraries in our TypeScript project. So, I'm going to
install Load Dash, which is a very popular utility library. You're probably familiar with it, but if not, it doesn't
really matter. So, let's install this. That was very quick. Now, back in index.ts, ts let's remove everything and
import everything as underscore from load dash now we immediately get a compilation error saying could not find
a declaration file for module load dash the reason we see this is because load dash is a pure JavaScript library it
doesn't have js comments it doesn't have declaration files so what can we do here well this is where we use a very popular
GitHub repository called definitely typed in this repository we can find declaration files for all the popular
JavaScript libraries. So back to the terminal here we install at types that is a definitely typed repository. Then
we type a forward slash followed by the name of the target package that is load dash. Now we want to install this as a
development dependency because we're not going to deploy this with our application. This is purely for build
time only for compile time. Okay. So here we can type d-savedev or I personally like to use
shortcuts. So I go with dash capital D. Okay. Now back to index.ts. The error is gone. And here we can call underscore
dot let's say clone. So here we have type annotation. So this function has a parameter called value of type unknown.
And it returns the same kind of value. So if we pass an array of numbers, you can see value becomes a number array and
here we get a number array in return. Okay. So this is low dash. Now with TypeScript getting more popular every
day, many JavaScript libraries come with declaration files. So we don't need to install their declaration files
separately from the definitely typed repository. Let me show you another example. So back to the terminal, let's
install chalk. Okay. Now back to our project, let's open the node modules folder.
Here's chalk. Now in the source folder, here we have index.js and right next to it we have
index.d.ts. So this is the declaration file for the index module. Welcome back to another section
of the ultimate TypeScript course. In this section, we're going to talk about building React applications with
TypeScript. So, we're going to build a relatively simple to-do app that shows the essential concepts you need to know
when building React applications with TypeScript. This is a good opportunity for you to see TypeScript in the real
world. So, now let's jump in and get [Music] started. All right, let's see how we can
create a React project with TypeScript. Now, before we get started, I should say that I'm assuming you know React and
just want to learn how to build React applications with TypeScript. So, in this section, I'm not going to explain
what React is and how it works. If you want to learn React, I have a comprehensive course on my website that
teaches you everything you need to know from the basics to more advanced concepts. So, here in the terminal, I'm
on my desktop. Let's run npx create React app. We're going to call this app reminders app. Now here we add d-
template followed by TypeScript. So this is going to create a React application configured to use
TypeScript. It's going to take a little while. So I'll be back shortly. All right, the installation is
done. So now let's change into this folder and open this with VS Code. All right, so here we have a basic React
project. As you can see, we have our TypeScript configuration file. We also have this source folder that contains
all our source code. Now, one thing that is different is that instead of JSX, we have TSX files. So, the same as JSX, but
here we have TypeScript and type safety. So, now that we have this project, next I'm going to show you how to add
Bootstrap to this project so we have a bit of styling so our application doesn't look horrendous.
[Music] All right. To add Bootstrap, first we're going to run npm install
Bootstrap. Good. Now, we're going to go in the source folder and open index.tsx. Now, on the top, just before
this line, we're going to import the Bootstrap CSS file. So, import Bootstrap/dist CSS/bootstrap.
CSS. Okay. Next, we're going to go to app.t CSS. I don't like any of those styles here, so I'm going to delete
everything and add a rule for the body element. Here, we're going to add a padding of 20 pixels. Now, to test that
everything works, let's add a button on our homepage. So, let's go to app.tsx. This is our main app component.
I'm going to delete everything here. We're just going to keep the parent div element. And here we're going to add a
button with two classes, btn and btn- primary. Again, I'm assuming you're familiar with React and Bootstrap. So,
I'm not going to explain how these things work. Now, let's press tab and give this button a label of click me.
Good. Now, to serve the application, we run npm start. All right. And here's our button.
Beautiful. So, Bootstrap is working. Next, we're going to create our first [Music]
component. All right. Now, let's create a component for showing a list of reminders. So, here in the source
folder, let's add a new folder called components. And right here, we add a new file called reminder
list.tsx. Now here in VS Code I have a very useful extension called ReactJS code snippets. So go to the extensions
panel and search for ReactJS code. You will find this extension called ReactJS code snippets. So with this installed we
can type rsf that is short for react stateless function. Now if you press enter we get a basic function component
that's very useful. Now here I'm using a function component because that's what most people are using these days. Now
here we have a compilation error over props saying parameter props implicitly has an any type. So to solve this
problem we need to use an interface to define the shape of props. So on the top we create an interface called reminder
list props. This is a naming convention a lot of people follow. So it's best to stick to this convention. Now what do we
need to pass to this component? A list of reminders. So we need another interface for defining the shape of our
reminders. So we can say each reminder is going to have an ID which is a number and a title which is a string. And then
we can say in our props object we're going to have a list of reminders or we could call it items whatever. This is
going to be a reminder array. Okay. Now, it's very likely that we're going to use this interface in other parts of our
application. So, I'm going to take it out of this file and put it somewhere central. So, back to our project here in
the source folder. Let's add a new folder called models. Now, some people prefer to call this entities or types or
interfaces. Whatever you want to call it, it doesn't really matter. So, here I'm going to add a new file called
reminder.ts. Now some people prefer to use Pascal naming convention here. So they name
this file with a capital R because in this file we're going to export a default type that is the reminder
interface. I'm not fussed about that. I prefer to use Pascal convention for naming my component files. So a
lowercase R works for me. Now back over here. Let's grab this interface and put it right here. And we're going to export
it as a default interface. Okay, now back to reminder list. Here we have a compilation error. So we press command
and period on Mac or control and period on Windows and import this interface. Good. So this interface is ready. Now we
can annotate props with reminder list props. The error is gone. So now in this component we're going to return an
unordered list and then we're going to map each item in the list of reminders to a list item. So we can say props do
items map. We can also use object dstructuring to simplify our code. So we can dstructure the props parameter and
pick the items property. That is better. Now we don't have to type props everywhere. So items map, we're going to
get each item and map it to a list item. Okay, now here's the interesting part. If you hover our mouse over item, we can
see that item is a reminder object. This is the benefit of using TypeScript in React projects. So all our objects are
properly typed. There is no guesswork. Okay, so we're going to render a bunch of list items. We're going to give each
a key, which is going to be item ID. And over here we're going to render item. This is another benefit of using
TypeScript. When we type dot, we can see all the properties of our object and their type. So we're going to render
item.title. Good. Now let's use this component in our app component. So let's go back to
app.tsx. We're going to replace this button with a reminder list. So reminder list. Now here we have a compilation
error because we haven't set the props. So we need to set items to an array of reminder objects. So first we need to
import the reminder interface. Then we define an array of reminders. And for now let's just add
one object in this array. The ID is going to be one and title is going to be reminder one. That's enough.
Now, we're going to pass an object right here. We're not going to do any state management yet. We just have a basic
array that we're going to pass to the items prop. Now, back to the browser. Let's refresh. Here's our
reminder list. Good. Next, we're going to talk about state [Music]
management. All right. Now let's talk about state management using the state hook. So over here we're going to call
the state hook to properly store our reminders in the app component. Now in react with TypeScript projects, this
function is generic. So here we can type angle brackets and specify the type of object we want to store here. In this
case, reminder array. Now let's use array dstructuring and grab reminders with set reminders. Now let's hover our
mouse over reminders and look at its type. So it can either be reminder array or undefined. The reason we see
undefined here is because we haven't initialized this state variable. So you know that in JavaScript when we call a
function if it don't supply any arguments by default undefined will be passed. So that's why the TypeScript
compiler thinks this object is either a reminder array or undefined. To solve this problem, all we need to do is pass
an empty array here. Now look, reminders can only be a reminder array. Okay. Now, when using the state hook, we don't
always have to supply a generic type argument. For example, if we call use state and initialize this with let's say
a boolean, true or false. Now, let's call this variable loading and set loading. Look at the type of loading.
It's a boolean. So in this case we don't have to explicitly supply this generic type
argument. Okay. So this is the proper way to manage the state of this component. And with this we should
remove this object from here and put it in this array. Well technically we don't need to do this. This is just for
demonstration. I want to have at least one object in this array so we can see something on the screen. Okay. So let's
remove this object. Good. So now that we have state in this component, next we're going to talk about calling the back end
to get a list of to-dos or [Music] reminders. In this lesson, we're going
to use a fake backend service called JSON placeholder. You can find it at JSON
placeholder.typode.com. On this website, we have various endpoints for getting a bunch of posts, comments, albums,
photos, to-dos, and users. So, in this lesson, we're going to use the to-dos endpoint for getting a bunch of to-dos
or reminders. Now, back to our project. In this component, we are not going to write any code for calling an API
because this component should only be responsible for rendering the UI, nothing else. other concerns like
calling an API or storing something in the local storage should be implemented in other classes. Okay, so back to our
project in the source folder, we're going to add a new folder called services. And here we're going to add a
new file called reminder.ts. Now to call the back end, we're going to use axis. This is not
necessary, but it's a library that I personally use often. So back to the terminal, let's install
axis. Good. Now on the top, we import axis from axis. Now access comes with type definition files. So we don't need
to install them separately. Okay. Now here we're going to define a class called reminder service. In this class,
first we're going to define a property called HTTP. We could call it anything, but HTTP makes sense. We're going to set
it to access.create. create. Now here we pass a configuration object. Look because we have TypeScript support in
axis we can see the type of this parameter which is axis request config. So if we press control and space here we
can see all the properties and type. So here I'm going to set base URL to now back to the browser. Let's grab this URL
and paste it right here. So that is our base URL. And now we're going to define a bunch of operations or methods for
getting to-dos, adding them, and removing them. So let's define a method called get
reminders. Here we call this HTTP.get. Now this get method is generic. So here we can type angle
brackets and specify the type of objects we expect to get. We're going to get a reminder array. Okay. Now here we
specify the URL or the endpoint which is slash todos. Now this returns a promise. So we need to await it to get the
response and we need to make this method async. And finally we return response data. Nothing fancy. You're probably
familiar with this approach. Now we need another method for adding a to-do. So add reminder takes a title of type
string. Here we call this http.post. We're going to post a reminder object.
Now for the URL again we're going to pass / todos and for the data we're going to pass an object with a title
property. Once again we await the call to get the response and finally we return response data and we make this
async and one last method that is remove reminder which takes an id of type number and here we
call this http delete we don't need to supply generic argument here we just pass a URL like
/todos plus id as simple Simple as that. Now let's await the call to get the response.
And here we return response data and make the same as sync. Okay. So our service is ready. The final step is to
export it. So export default. Now instead of exporting the reminder service class, we're going to export an
instance of this class. With this, the consumers of this module don't have to new up an instance of this service every
time. Okay, it's just a little bit easier. So, we're done with this step. Next, I'm going to show you how to use
the effect hook to get the list of reminders when our application [Music]
starts. So, we created our service. Now, here in app module, let's import it. Import reminder service from We go to
the services folder and grab reminder. Now, in the app component, first I'm going to remove this hard-coded
reminder. We don't need it anymore because we're going to fetch all of the reminders from the server. Okay. Now,
over here, we're going to use the effect hook. So, use effect. We give it a call back. And to make sure that this gets
executed only once, we pass an empty array here. Again, at this point, you should be familiar with all these little
details. If not, go back and watch my React course. So in this callback we're going to call
reminder service get reminders. Now this returns a promise. So we need to await it and make this
async. Now the problem is that this callback that we pass to the effect hook cannot be an async function. So we need
to extract this and put it somewhere else. So let's define a function expression. We're going to call that
load reminders like this. So we await the call and get our reminders and then we
call set reminders and give it the reminders that we get from the server. Now we pass a regular call back here and
over here we call load reminders without awaiting it. Okay, so let's see what we get back to the browser. Refresh. Here
are all of our reminders. Beautiful. Now, the last thing I want to do in this lesson is applying a couple of Bootstrap
classes to make this look a little bit nicer. So, let's go to the reminder list component. Okay, here is our UL. I'm
going to give this a class called list group. And for our LI, we're going to apply a class called list
group item. Let's try again. Beautiful. [Music]
All right. Now, let's add a button for deleting reminders. So, here in the reminder list component, I want to add a
button next to each reminder. So, here's our list item. I'm going to break this down so we can see clearly. So, right
after the title, I want to add a button with a couple of classes, btn and btn danger, to make it look red. And let's
call this delete. So here's what we get. It's not too bad, but it's a little bit too red. So let's use an outline button.
So I'm going to change the second class to btn outline danger. Look, that is better. Now the button is currently too
close to the text. So let's add some horizontal margin. For that, we're going to use a Bootstrap class called M for
margin, X for horizontal, and two, which is the amount of margin we're going to apply. Now, that is better. Now, the
last thing I want to do here is making the corners round. And for that, we're going to use another class called
rounded pill. That is much better. Now, let's implement deleting reminders. So you
know that reminder list is only responsible for rendering the list of reminders. So we're not going to delete
reminders in this component because the state lives in the app component. Right? So this is where we're going to change
the state. So here we're going to define a function for deleting reminders and then we're going to pass that function
as a callback to the reminderless component. So let's define a function
called remove reminder. This function should take the ID which is a number. And for now let's just log the
ID on the console. Okay. Now we want to pass this function as a call back to the reminder list. So first we need to
change the props interface. Here we're going to add a new property called on remove reminder which is a function that
takes a number and returns void. Okay. Now back to the app component. We're going to set on remove reminder to
remove reminder. Okay. Now the final part is to go in the reminderless component and handle the click event of
this button. So we set click to here we pass an arrow function and here we need to call on remove reminder. So let's
grab it here on remove reminder and then we call on remove reminder and give item do id. So item is our loop variable.
Okay. Now I made a mistake here. This should be on click.
Okay. Good. So let's test our implementation up to this point. Back in the browser. Delete. Delete. Delete. Now
let's look at the console. So here are the ideas of the reminders I tried to delete. So all the plumbing is working.
Now the final part is to actually delete these reminders. So back in the app component in this function, we're going
to go to our reminders array and use the filter method to get all reminders except the one with this ID. So reminder
ID does not equal ID. And then we can pass the result to set reminders. As simple as that. Now, let's test it. So,
delete, delete, delete. Beautiful. So, deleting is working. Next, we're going to implement the ability to add
[Music] reminders. All right. Now, let's create a form for adding a new reminder to this
list. So, let's add a new component called new reminder. Once again, we type rsf to generate a
function component. Good. Now, we have a compilation error on props. I'm going to temporarily remove props. We'll come
back to it shortly. Now, one thing I forgot to tell you earlier is that here we can annotate the return value of our
function components with jsx.element. And with this, if we don't return something or if we return a
different kind of object, we get a compilation error. So by annotating the return value of our function components
with JSX.element, the TypeScript compiler will ensure that we return a JSX element from our function
components. Okay, it's not necessary, but it's a good practice to follow. So let's bring this back. Okay, now instead
of a div, we're going to return a form. Now inside this form, we're going to add a label. Next to that, we're going to
add an input with the class of form-cont control. And next to that we're going to add a button with two classes btn and
btn- primary. Now we can also give it round corners by applying rounded peel. That's better. Now if you press tab we
get the markup. Beautiful. Let me reformat it so you can see clearly. Okay. Now I'm going to give
this input the ID of title and use the same value for the HTML 4 attribute of the label.
With this when the user clicks on the label the input will get focus. Okay. Now we should make this a submit button.
So we set type to submit. Okay. We should also give it a label like add reminder. I think that's good for now.
So let's add this to our app component. We go to app.tsx and right above the list we add
the new reminder component. Good. Let's see what we get. Okay. The button is too close to the text box and the list. So,
let's give it a vertical margin. So, back to the new reminder component. Here's our button. Let's apply a
vertical margin. So, M for margin, Y for vertical, and let's say three for the amount. Okay, that's better. That's
reasonable. So, I'm happy with how the form looks. Now, we need to manage the state of this input box.
So, back to this component. We're going to use the state hook. use state. We're going to initialize this to an empty
string and then we use dstructuring to grab title and set title. Now we need to do two things here. First we need to set
the value property to title and second we need to handle the unchange event. So over here we get an event and here we
call set title to e.target. Now note that here we have IntelliSense. So if you type E dot, we can see all the
properties and methods of this event object. If you hover our mouse over E, we can see E is an instance of React the
change event which is a generic type. Okay. So here we're going to access E.target dot value. Okay, our form is
ready. Next we're going to handle form submission. All [Music]
right. Now to handle the form submission here, we set unsubmit to a function. So let's define a function called submit
form that takes an event of type react form event. So in the previous lesson we talked about the change event interface.
Here we have the form event interface. Now we say e dot prevent default to prevent the default submission of the
form. So we can do everything on the client and once again here we have intellisense. So if you type e dot we
can see all the properties and methods of this event object. Beautiful. So e.trevent default. Now before going
further let's just log the title on the console to make sure all this plumbing works all the basic integration works.
So let's log the title and then we pass submit form over here. Let's test our implementation. So back in the browser,
let's bring up the console. Type something click. Okay, we have the title. Beautiful. Now let's implement
adding a reminder. Now we're not going to implement that logic in this component because that's the
responsibility of the app component because this is where the state lives, right? So, similar to removing
reminders, we're going to define a function for adding a reminder and then pass that as a call back to our child
component. So, let's define a function called add reminder that takes a title of type string. Now, once again, before
getting distracted with too much complexity around adding a reminder, I just want to log the title on the
console to make sure that the integration between these components works. So, we log the title. Then we
need to pass this function as a prop to the new reminder component. So on add reminder we set it to add
reminder. Now we have a compilation error because this is not a valid prop for the new reminder component. So back
to this component, let's define an interface called new reminder props. Here we need a property called on add
reminder which is a function that takes title as a string and returns void. Now let's add props to this function and
annotated with new reminder props. Now again we can use object structuring to grab on add reminder and then instead of
logging the title on the console here we simply call our call back. So on add reminder and we give it the title. Let's
make sure our implementation works up to this point. So back to the browser, I'm going to change this to be add reminder.
We still see the result on the console. Beautiful. Now we're ready to call the server and add this reminder. So back to
the app component. Instead of logging the title on the console, we're going to call reminder
service that add reminder. And here we pass the title. Next we need to await the call and get the new reminder which
has an ID generated by the server. Now because we have used await here we need to make this async and finally we call
set reminders. Give it a new array with the new reminder and all the existing reminders. So back to the browser let's
add a new reminder here. Okay, it works beautiful. And we can also delete it. Perfect. Now the final thing I want to
implement here is clearing this input field upon adding a reminder. So back to the new reminder component after we call
on add reminder. Here we set title to an empty string. Let's test it. So one more time. Okay, the input is cleared.
Beautiful. Now what if we submit the form without providing a title for this reminder?
That's not good. So, we want to create a reminder only if the title has a value. So, back to our form handler right here.
We're going to do some basic validation. We can say if title is not truthy, then we're going to return. Now, back to the
browser. Let's test this. So, nothing happens. Beautiful. So, we're done with this application. Of course, we could
take it to the next level. We could implement a loading indicator. We could handle HTTP errors. So if you cannot
connect to the server then we want to show some error to the user right but all of that is really outside the scope
of this course because here our focus is on react and TypeScript integration we're not building a full-blown React
application so I leave it up to you as an additional exercise. We have reached the end of
this course. Thank you so much for taking this course and I hope you have learned a lot and I'm going to see you
again in my other courses. If you enjoy this course, please support me by telling others about my coding school. I
really appreciate your support. Once again, thank you and I wish you all the best.
This course requires only basic JavaScript familiarity; no prior TypeScript knowledge is necessary, making it suitable for beginners who want to learn TypeScript from scratch.
You should install Node.js and the TypeScript compiler globally using npm, and use Visual Studio Code for an optimized coding experience. Configure your project with a tsconfig.json file to customize compiler options and compile your TypeScript code using the 'tsc' command.
TypeScript adds static typing to JavaScript, allowing you to catch errors at compile time. It includes core types like primitives, arrays, enums, and advanced features such as union and intersection types, type aliases, generics, and support for object-oriented concepts like classes and interfaces, enhancing code reliability and maintainability.
The course guides you to create React projects configured with TypeScript, showing how to define functional components with typed props and state via interfaces. It covers using React hooks (useState, useEffect) with type annotations, managing component state and events with strong typing, and implementing typed CRUD operations using Axios for robust React applications.
Advanced topics include generics for creating reusable type-safe functions and classes with constraints, decorators to augment classes and methods, and type mapping for dynamic type modification. It also explains object-oriented programming features like access modifiers, inheritance, abstract classes, and interfaces to structure scalable applications.
You can include JavaScript files by enabling 'allowJs' and 'checkJs' in your tsconfig. Use JSDoc comments to add type information or create declaration (*.d.ts) files for typing external JS modules. For third-party libraries, use DefinitelyTyped type declarations to ensure type safety within your TypeScript project.
The course encourages enabling strict compiler options for thorough type checking, avoiding the 'any' type when possible, and leveraging TypeScript's type system fully with interfaces, type aliases, and generics. Applying consistent typing for functions, props, and state, writing clear object-oriented code, and integrating TypeScript incrementally promotes robust and maintainable applications.
Heads up!
This summary and transcript were automatically generated using AI with the Free YouTube Transcript Summary Tool by LunaNotes.
Generate a summary for freeRelated Summaries
Maîtrisez React : JSX, ReactDOM et création de composants
Découvrez les bases essentielles pour débuter avec React dans cette vidéo complète. Apprenez le fonctionnement du JSX, la gestion du ReactDOM, ainsi que la création et l'utilisation efficace des composants React. Ce guide pratique est accompagné de conseils pour optimiser votre code, gérer les mises à jour et structurer vos projets React.
Building a Live Score Application with React and Tailwind CSS: Boot Camp Overview
In this boot camp session, participants will learn to build a live score application using React and Tailwind CSS. The session covers the fundamentals of React, including JSX, components, props, and state management, along with practical coding examples and installation guidance.
Unlocking the Power of Go: A Comprehensive Programming Course for Beginners
Learn Go programming with our comprehensive course for beginners. Master the fundamentals and build real-world projects!
Comprehensive Python Course: From Basics to Advanced Mega Projects
This extensive Python course covers everything from fundamental programming concepts, data types, and control flow to advanced topics like OOP, file handling, virtual environments, and AI integration. Featuring practical projects including a Jarvis assistant and chatbot, it equips learners with hands-on skills for professional growth and job readiness.
Top 10 Angular Interview Questions for Beginners and Juniors
Master the top 10 Angular interview questions designed for candidates with 0-2 years experience. Learn key concepts like Angular vs AngularJS, TypeScript advantages, components, modules, data binding, directives, lifecycle hooks, observables, and authentication to confidently crack your interview.
Most Viewed Summaries
Kolonyalismo at Imperyalismo: Ang Kasaysayan ng Pagsakop sa Pilipinas
Tuklasin ang kasaysayan ng kolonyalismo at imperyalismo sa Pilipinas sa pamamagitan ni Ferdinand Magellan.
A Comprehensive Guide to Using Stable Diffusion Forge UI
Explore the Stable Diffusion Forge UI, customizable settings, models, and more to enhance your image generation experience.
Mastering Inpainting with Stable Diffusion: Fix Mistakes and Enhance Your Images
Learn to fix mistakes and enhance images with Stable Diffusion's inpainting features effectively.
Pamamaraan at Patakarang Kolonyal ng mga Espanyol sa Pilipinas
Tuklasin ang mga pamamaraan at patakaran ng mga Espanyol sa Pilipinas, at ang epekto nito sa mga Pilipino.
Pamaraan at Patakarang Kolonyal ng mga Espanyol sa Pilipinas
Tuklasin ang mga pamamaraan at patakarang kolonyal ng mga Espanyol sa Pilipinas at ang mga epekto nito sa mga Pilipino.

