In this post, I provide a quick tour of the most important basic features of the JavaScript language as they apply to Angular development. I don’t have the space to describe JavaScript completely, so I have focused on the essentials that you’ll need to get up to speed and follow the examples on this website.
Understanding the Basic Workflow
Writing the word Hello to the JavaScript console is a simple example, but there is a lot going on behind the scenes. To get a sense of the development workflow, add the statement shown in the following example to the main.ts file.
console.log("Hello");
console.log("Apples");
When you save the change to the main.ts file, the following process occurs:
- The TypeScript compiler will detect the change to the main.ts file and compile it to generate a new main.js file that can run in any browser. The code that is produced is combined with the other JavaScript code produced by the compiler into a single file called a bundle.
- The development HTTP server detects the change to the bundle file and signals the browser to reload the HTML document.
- The browser reloads the HTML document and starts processing the elements it contains. It loads the JavaScript files specified by the script elements in the HTML document, including one that specifies the bundle file that contains the statements from the main.ts file.
- The browser executes the statements that were originally in the main.ts file, which writes out two messages to the browser’s JavaScript console.
The overall result is that you will see the following messages displayed:
Hello
Apples
This may seem like a large number of steps for a simple application, but this process allows TypeScript features to be used and automatically takes care of detecting changes, running the compiler, and updating the browser.
Using Statements
The basic JavaScript building block is the statement. Each statement represents a single command, and statements are usually terminated by a semicolon (;). The semicolon is optional, but using them makes your code easier to read and allows for multiple statements on a single line. In the following example, I have added a pair of statements to the JavaScript file.
console.log("Hello");
console.log("Apples");
console.log("This is a statement");
console.log("This is also a statement");
The browser executes each statement in turn. In this example, all the statements simply write messages to the console. The results are as follows:
Hello
Apples
This is a statement
This is also a statement
Defining and Using Functions
When the browser receives JavaScript code, it executes the statements it contains in the order in which they have been defined. This is what happened in the previous example. The browser loader loaded the main.js file, and the statements it contains were executed one by one, all of which wrote a message to the console.
You can also package statements into a function, which won’t be executed until the browser encounters a statement that invokes the function, as shown in the following example.
let myFunc = function () {
console.log("This is a statement");
};
myFunc();
Defining a function simply: use the let keyword followed by the name you want to give the function, followed by the equal sign (=) and the function keyword, followed by parentheses (the ( and ) characters). The statements you want the function to contain are enclosed between braces (the { and } characters).
In the example, I used the name myFunc, and the function contains a single statement that writes a message to the JavaScript console. The statement in the function won’t be executed until the browser reaches another statement that calls the myFunc function, like this:
...
myFunc();
...
Executing the statement in the function produces the following output:
This is a statement
Other than demonstrating how functions are defined, this example isn’t especially useful because the function is invoked immediately after it has been defined. Functions are much more useful when they are invoked in response to some kind of change or event, such as user interaction.
THE OTHER WAY TO DEFINE FUNCTIONS
There are two ways in which you can define functions in JavaScript. The approach I used in the above example is known as a function expression. The same function can also be defined like this:
...
function myFunc() {
console.log("This is a statement");
}
...
This is known as a function declaration. The result is the same: a function called myFunc that writes a message to the console. The difference is how the functions are processed by the browser when a JavaScript file is loaded. Function declarations are processed before the code in a JavaScript file is executed, which means you can use a statement that calls a function before it is defined, like this:
...
myFunc();
function myFunc() {
console.log("This is a statement");
}
...
This works because the browser finds the function declaration when it parses the JavaScript file and sets up the function before the remaining statements are executed, a process known as function hoisting. Function expressions, however, are not subject to hoisting, which means that this code will not work:
...
myFunc();
let myFunc = function() {
console.log("This is a statement");
};
...
This code will generate an error reporting that myFunc is not a function. Developers who are new to JavaScript tend to prefer using function declarations because the syntax is more consistent with languages like C# or Java. The technique you use is entirely up to you, although you should aim to be consistent throughout your project to make your code easier to understand.
Defining Functions with Parameters
JavaScript allows you to define parameters for functions, as shown in the following example.
let myFunc = function(name, weather) {
console.log("Hello " + name + ".");
console.log("It is " + weather + " today");
};
myFunc("Adam", "sunny");
I added two parameters to the myFunc function, called name and weather. JavaScript is a dynamically typed language, which means you don’t have to declare the data type of the parameters when you define the function. I’ll come back to dynamic typing later in the chapter when I cover JavaScript variables. To invoke a function with parameters, you provide values as arguments when you invoke the function, like this:
...
myFunc("Adam", "sunny");
...
The results from this listing are as follows:
Hello Adam
It is sunny today
Using Default and Rest Parameters
The number of arguments you provide when you invoke a function doesn’t need to match the number of parameters in the function. If you call the function with fewer arguments than it has parameters, then the value of any parameters you have not supplied values for is undefined, which is a special JavaScript value. If you call the function with more arguments than there are parameters, then the additional arguments are ignored.
The consequence of this is that you can’t create two functions with the same name and different parameters and expect JavaScript to differentiate between them based on the arguments you provide when invoking the function. This is called polymorphism, and although it is supported in languages such as Java and C#, it isn’t available in JavaScript. Instead, if you define two functions with the same name, then the second definition replaces the first.
There are two ways that you can modify a function to respond to a mismatch between the number of parameters it defines and the number of arguments used to invoke it. Default parameters deal with the situation where there are fewer arguments than parameters, and they allow you to provide a default value for the parameters for which there are no arguments, as shown in the following example.
let myFunc = function (name, weather = "raining") {
console.log("Hello " + name + ".");
console.log("It is " + weather + " today");
};
myFunc("Adam");
The weather parameter in the function has been assigned a default value of raining, which will be used if the function is invoked with only one argument, producing the following results:
Hello Adam.
It is raining today
Rest parameters are used to capture any additional arguments when a function is invoked with additional arguments, as shown in the example.
let myFunc = function (name, weather, ...extraArgs) {
console.log("Hello " + name + ".");
console.log("It is " + weather + " today");
for (let i = 0; i < extraArgs.length; i++) {
console.log("Extra Arg: " + extraArgs[i]);
}
};
myFunc("Adam", "sunny", "one", "two", "three");
The rest parameter must be the last parameter defined by the function, and its name is prefixed with an ellipsis (three periods, …). The rest parameter is an array to which any extra arguments will be assigned. In the listing, the function prints out each extra argument to the console, producing the following results:
Hello Adam.
It is sunny today
Extra Arg: one
Extra Arg: two
Extra Arg: three
Defining Functions That Return Results
You can return results from functions using the return keyword. The following example shows a function that returns a result.
let myFunc = function(name) {
return ("Hello " + name + ".");
};
console.log(myFunc("Adam"));
This function defines one parameter and uses it to produce a result. I invoke the function and pass the result as the argument to the console.log function, like this:
...
console.log(myFunc("Adam"));
...
Notice that you don’t have to declare that the function will return a result or denote the data type of the result. The result of this listing is as follows:
Hello Adam.
Using Functions As Arguments to Other Functions
JavaScript functions can be passed around as objects, which means you can use one function as the argument to another, as demonstrated in the following example.
let myFunc = function (nameFunction) {
return ("Hello " + nameFunction() + ".");
};
console.log(myFunc(function () {
return "Adam";
}));
The myFunc function defines a parameter called nameFunction that it invokes to get the value to insert into the string that it returns. I pass a function that returns Adam as the argument to myFunc, which produces the following output:
Hello Adam.
Functions can be chained together, building up more complex functionality from small and easily tested pieces of code, as shown in the following example.
let myFunc = function (nameFunction) {
return ("Hello " + nameFunction() + ".");
};
let printName = function (nameFunction, printFunction) {
printFunction(myFunc(nameFunction));
}
printName(function () { return "Adam" }, console.log);
Using Arrow Functions
Arrow functions—also known as fat arrow functions or lambda expressions—are an alternative way of defining functions and are often used to define functions that are used only as arguments to other functions. The following example replaces the functions from the previous example with arrow functions.
let myFunc = (nameFunction) => ("Hello " + nameFunction() + ".");
let printName = (nameFunction, printFunction) => printFunction(myFunc(nameFunction));
printName(function () { return "Adam" }, console.log);
These functions perform the same work like the ones above example. There are three parts to an arrow function: the input parameters, then an equal sign and a greater-than sign (the “arrow”), and finally the function result. The return keyword and curly braces are required only if the arrow function needs to execute more than one statement.