Book Image

Learning Angular - Fourth Edition

By : Aristeidis Bampakos, Pablo Deeleman
5 (1)
Book Image

Learning Angular - Fourth Edition

5 (1)
By: Aristeidis Bampakos, Pablo Deeleman

Overview of this book

As Angular continues to reign as one of the top JavaScript frameworks, more developers are seeking out the best way to get started with this extraordinarily flexible and secure framework. Learning Angular, now in its fourth edition, will show you how you can use it to achieve cross-platform high performance with the latest web techniques, extensive integration with modern web standards, and integrated development environments (IDEs). The book is especially useful for those new to Angular and will help you to get to grips with the bare bones of the framework to start developing Angular apps. You'll learn how to develop apps by harnessing the power of the Angular command-line interface (CLI), write unit tests, style your apps by following the Material Design guidelines, and finally, deploy them to a hosting provider. Updated for Angular 15, this new edition covers lots of new features and tutorials that address the current frontend web development challenges. You’ll find a new dedicated chapter on observables and RxJS, more on error handling and debugging in Angular, and new real-life examples. By the end of this book, you’ll not only be able to create Angular applications with TypeScript from scratch, but also enhance your coding skills with best practices.
Table of Contents (17 chapters)
15
Other Books You May Enjoy
16
Index

Functions, lambdas, and execution flow

Functions are the processing machines we use to analyze input, digest information, and apply the necessary transformations to data. Data can be provided either to transform the state of our application or to return an output that will be used to shape our application’s business logic or user interactivity.

Functions in TypeScript are not that different from regular JavaScript, except that, like everything else in TypeScript, they can be annotated with static types. Thus, they improve the compiler by providing the information it expects in their signature and the data type it aims to return, if any.

Annotating types in functions

The following example showcases how a regular function is annotated in TypeScript:

function sayHello(name: string): string {
    return 'Hello, ' + name;
}

There are two main differences from the usual function syntax in regular JavaScript. First, we annotate the parameters declared in the function signature, which makes sense since the compiler will want to check whether the data provided holds the correct type. In addition to this, we also annotate the returning value by adding the string type to the function declaration.

As mentioned in the previous section, the TypeScript compiler is smart enough to infer types when no annotation is provided. In this case, the compiler looks into the arguments provided and returns statements to infer a returning type from them.

Functions in TypeScript can also be represented as expressions of anonymous functions, where we bind the function declaration to a variable:

const sayHello = function(name: string): string {
    return 'Hello, ' + name;
}  

However, there is a downside to that approach. Although typing function expressions this way is allowed, thanks to type inference, the compiler is missing the type definition of the declared variable. We might assume that the inferred type of a variable that points to a function typed as a string is a string. Well, it’s not. A variable that points to an anonymous function ought to be annotated with a function type:

const sayHello: (name: string) => string = function(name: string): string {
    return 'Hello, ' + name;
}

The function type informs us of the types expected in the payload and the type returned by the function execution, if any. This whole block, which is of the form (arguments: type) => returned type, becomes the type annotation that our compiler expects.

Function parameters in TypeScript

Due to the type checking performed by the compiler, function parameters require special attention in TypeScript.

Optional parameters

Parameters are a core part of the type checking applied by the TypeScript compiler. Parameters are defined as optional by adding the character ? after the parameter name:

function greetMe(name: string, greeting?: string): string {
    if (!greeting) {
        greeting = 'Hello';
    }
    return greeting + ', ' + name;
}

To call the previous function, we can omit the second parameter in the function call:

greetMe('John');

So, an optional parameter is not set unless you explicitly pass it a value. It is more of a construct so that you can get help deciding what parameters are mandatory and which ones are optional. To exemplify this, consider the following function signature:

function add(mandatory: string, optional?: number) {}

We can invoke the previous function in the following ways:

add('some string');
add('some string', 3.14);

Both versions are valid. Be aware that optional parameters should be placed last in a function signature. Consider the following function:

function add(optional?: number, mandatory: string) {}

Both parameters of the previous function would be considered mandatory. Suppose that we call the function like so:

add(1);

The compiler would complain that you have not provided a value for the mandatory parameter. Remember, optional arguments are great, but place them last.

Default parameters

TypeScript gives us another feature to cope with default parameters, where we can set a default value that the parameter assumes when it’s not explicitly passed upon executing the function. The syntax is pretty straightforward, as we can see when we refactor the previous example:

function greetMe(name: string, greeting: string = 'Hello'): string {
    return `${greeting}, ${name}`;
}

Like optional parameters, default parameters must be put right after the required parameters in the function signature.

Rest parameters

One of the significant advantages of JavaScript flexibility when defining functions is the ability to accept an unlimited non-declared array of parameters. TypeScript can achieve the same behavior using the rest syntax. Essentially, we can define an additional parameter at the end of the arguments list prefixed by an ellipsis () and typed as an array:

function greetPeople(greeting: string, ...names: string[]): string {
    return greeting + ', ' + names.join(' and ') + '!';
}

Rest parameters are beneficial when we don’t know how many arguments will be passed in.

Function overloading

Method and function overloading are typical in other languages, such as C#. However, implementing this functionality in TypeScript clashes with the fact that JavaScript, which TypeScript is meant to compile to, does not implement any elegant way to integrate it out of the box. So, the only workaround possible requires writing function declarations for each of the overloads and then writing a general-purpose function that wraps the actual implementation, whose list of typed arguments and return types are compatible with all the others:

function hello(names: string): string
function hello(names: string[]): string
function hello(names: any, greeting?: string): string {
    let namesArray: string[];
    if (Array.isArray(names)) {
        namesArray = names;
    } else {
        namesArray = [names];
    }
    if (!greeting) {
        greeting = 'Hello';
    }
    return greeting + ', ' + namesArray.join(' and ') + '!';
}

In the preceding example, we create three different function signatures, each of which features different type annotations. We could even define different return types by annotating the wrapping function with type any.

Arrow functions

ES6 introduced the concept of fat arrow functions (also called lambda functions in other languages such as Python, C#, Java, or C++). The purpose of the arrow function is to simplify the general function syntax and provide a bulletproof way to handle the function scope traditionally handled by the this keyword. The first thing we notice is its minimalistic syntax, where, most of the time, we see arrow functions as single-line, anonymous expressions:

const double = x => x * 2;

The preceding function computes the double of a given number x and returns the result, although we do not see any function or return statements in the expression. If the function signature contains more than one argument, we need to wrap them all between parentheses:

const add = (x, y) => x + y;

Arrow functions can also contain statements by wrapping the whole implementation in curly braces:

const addAndDouble = (x, y) => {
    const sum = x + y;
    return sum * 2;
}

Still, what does this have to do with scope handling? The value of this can point to a different context, depending on where we execute the function. When we refer to this inside a callback, we lose track of the upper context, which usually leads us to use conventions such as assigning its value to a variable named self or that. It is this variable that is used later on within the callback. Statements containing interval or timeout functions make for a perfect example of this:

function delayedGreeting(name): void {
    this.name = name;
    this.greet = function(){
        setTimeout(function() {
            console.log('Hello ' + this.name);
        }, 0);
    }
}
const greeting = new delayedGreeting('John');
greeting.greet();

If we execute the preceding script, it won’t print the name John in the browser console as expected because it modifies the scope of this when evaluating the function inside the timeout call. If we modify the code according to the following, it will do the trick:

function delayedGreeting(name): void {
    this.name = name;
    this.greet = function() {
        setTimeout(() => 
            console.log('Hello ' + this.name)
        , 0);
    }
}

Even if we break down the statement contained in the arrow function into several lines of code wrapped by curly braces, the scope of this keeps pointing to the instance itself outside the timeout call, allowing for more elegant and clean syntax.

Now that we have acquired basic knowledge of functions in TypeScript and how they are executed, we can continue our journey to the typing system and learn some of the most common TypeScript features used in Angular.