Book Image

Expert Angular

By : Sridhar Rao Chivukula
Book Image

Expert Angular

By: Sridhar Rao Chivukula

Overview of this book

Got some experience of Angular under your belt? Want to learn everything about using advanced features for developing websites? This book is everything you need for the deep understanding of Angular that will set you apart from the developer crowd. Angular has introduced a new way to build applications. Creating complex and rich web applications, with a lighter resource footprint, has never been easier or faster. Angular is now at release 4, with significant changes through previous versions. This book has been written and tested for Angular release 4. Angular is a mature technology, and you'll likely have applications built with earlier versions. This book starts by showing you best practices and approaches to migrating your existing Angular applications so that you can be immediately up-to-date. You will take an in-depth look at components and see how to control the user journey in your applications by implementing routing and navigation. You will learn how to work with asynchronous programming by using Observables. To easily build applications that look great, you will learn all about template syntax and how to beautify applications with Material Design. Mastering forms and data binding will further speed up your application development time. Learning about managing services and animations will help you to progressively enhance your applications. Next you’ll use native directives to integrate Bootstrap with Angular. You will see the best ways to test your application with the leading options such as Jasmine and Protractor. At the end of the book, you’ll learn how to apply design patterns in Angular, and see the benefits they will bring to your development.
Table of Contents (18 chapters)

Basics of TypeScript

TypeScript is a superset of JavaScript and is an open source language developed by Microsoft. Code written in TypeScript will be compiled to JavaScript and executed on any browser or server running Node.js. TypeScript is actually a type of JavaScript. TypeScript helps to improve the quality of code you write in JavaScript. If we use external libraries, we need to use type definition files for the imported libraries. Type definition files provide JavaScript tooling support and also enable compile time checks, code refactoring, and variable renaming support by inferring the structure of the code. TypeScript is evolving and keeps adding additional features aligned with the ES2016 specification and later.

There are various editors available on the market that write TypeScript code and compile them using a TypeScript compiler. These editors take care of compiling your TypeScript into JavaScript. Some popular editors are shown here:

  • Visual Studio
  • Visual Studio Code
  • Sublime text
  • Atom
  • Eclipse
  • Emacs
  • WebStorm
  • Vim

You can also download TypeScript as a Node.js package by executing the following command in the Node.js command-line tool to install TypeScript globally:

npm install -g typescript

To transpile the TypeScript code into JavaScript, you can execute the following command in the command-line tool:

tsc mytypescriptcodefile.ts

Here, tsc is the TypeScript compiler that converts a TypeScript file into a JavaScript file. mytypescriptfile is the name of your TypeScript code file and .ts is the extension of the TypeScript file. On executing the tsc command, it generates a .js file with the same name as the .ts source file.

We will be using Visual Studio Code editor for our sample code demos in this chapter. Let us see basic features of TypeScript with examples.

Basic types

Let's explore some of the basic types in TypeScript and how to use them. Basic types include primitive types such as number, string, boolean, and array in TypeScript. JavaScript only validates types during runtime, but TypeScript validates variable types during compile time and greatly reduces the possibility of typecast issues during runtime.

Number type

The number type represents floating point values. It can hold values such as decimal, binary, hexadecimal, and octal literals:

let decimal: number = 6; 
let hex: number = 0xf00d; 
let binary: number = 0b1010; 
let octal: number = 0o744; 

Boolean type

The Boolean type is a very simple type that can hold either of two values, true or false. This Boolean type is used to maintain the state in a variable:

let isSaved: Boolean; 
isSaved = true; 

Here, the isSaved variable of type Boolean is assigned with the value true.

String

The string data type can hold a sequence of characters. Declaring and initializing the string variable is very simple, as follows:

var authorName: string = "Rajesh Gunasundaram"; 

Here, we declared a variable named authorName as a string, and it is assigned the value Rajesh Gunasundaram. TypeScript supports surrounding the string value with either a double quotes (") or single quotes (').

Array

The array data type is meant to hold a collection of values of specific types. In TypeScript, we can define arrays in two ways, which are as follows:

var even:number[] = [2, 4, 6, 8, 10]; 

This statement declares an array variable of the number type using square brackets ([]) after the data type number, and it is assigned with a series of even numbers from 2 to 10. The second way to define array is as follows:

var even:Array<number> = [2, 4, 6, 8, 10]; 

This statement uses the generic array type, which uses the Array keyword followed by angle brackets (<>) that wrap the number data type.

Enum

The enum data type will have a named set of values. We use enumerators to give user-friendly names to constants that identify certain values:

enum Day {Mon, Tue, Wed, Thu, Fri, Sat, Sun}; 
var firstDay: Day = Day.Mon; 

Here, we have the Day enum variable, which holds a series of values that represent each day of the week. The second statement shows how to access a particular enum value in a day and assign it to another variable.

Any

The any data type is a dynamic data type that can hold any value. TypeScript throws compile time errors if you assign a string variable to an integer variable. If you are not sure about what value a variable is going to hold and you would like to opt out of compiler-checking for the type in the assignment, you can use the any data type:

var mixedList:any[] = [1, "I am string", false]; 
mixedList [2] = "no you are not"; 

Here, we used an array of the any type so that it can hold any type, such as numbers, strings, and booleans.

Void

Void is actually nothing. It can be used as the return type of a function to declare that this function will not return any value:

function alertMessage(): void { 
    alert("This function does not return any value"); 
} 

Classes

A class is an extensible template that is used to create objects with member variables to hold the state of the object and member functions that deal with the behavior of the object.

JavaScript only supports function-based and prototype-based inheritance to build reusable components. ECMAScript 6 provides the syntactic sugar of using classes in supporting object-oriented programming. However, not all browsers understand ES6 and we need transpilers, such as TypeScript, that compile the code down to JavaScript and target ES5, which is compatible with all browsers and platforms:

class Customer { 
    name: string; 
    constructor(name: string) { 
        this.name = name; 
   } 
    logCustomer() { 
        console.log('customer name is ' + this.name; 
   } 
} 
  
var customer = new Customer("Rajesh Gunasundaram"); 

This Customer class has three members: a name property, a constructor, and a logCustomer method. The last statement outside the customer class creates an instance of the customer class using the new keyword.

Interfaces

An interface is an abstract type that defines the behavior of a class. An interface is a contract that abstracts the implementation. An interface provides a type definition for an object that can be exchanged between clients. This enables the client to only exchange an object that is complied with the interface type definition. Otherwise, we get a compile time error.

In TypeScript, interfaces define contracts for an object within your code and the code outside your project. Let's see how to use TypeScript with an example:

function addCustomer(customerObj: {name: string}) { 
  console.log(customerObj.name); 
} 
  
var customer = {id: 101, name: "Rajesh Gunasundaram"}; 
addCustomer(customer); 

The type checker verifies the addCustomer method call and examines its parameter. addCustomer expects an object with the name property of the string type. But the client that calls addCustomer is passed an object with two parameters, id and name, respectively.

However, the compiler does not check the id property as it is not available in the parameter type of the addCustomer method. It only matters for the compiler that the required properties are present.

Let's rewrite the method applying interface as a parameter type as follows:

interface Customer { 
  name: string; 
} 
  
function addCustomer(customerObj: Customer) { 
  console.log(customerObj.name); 
} 
var customer = {id: 101, name: "Rajesh Gunasundaram"}; addCustomer(customer);

Here, we declared the Customer interface with the name parameter, and we modified the addCustomer signature to accept the parameter of the type Customer interface. The remaining statements are same as in the previous code snippet. The compiler only checks for the shape of the object as TypeScript implements the structural type system. It will not check whether the object we are passing implements the Customer interface. It only looks for the name property of the string type in the parameter and then allows it, if it's present.

Optional properties using an interface

In some scenarios, we may want to pass values only for minimal parameters. In such cases, we can define the properties in an interface as optional properties, as follows:

interface Customer { 
  id: number; 
  name: string; 
  bonus?: number; 
} 
  
function addCustomer(customer: Customer) { 
  if (customer.bonus) { 
    console.log(customer.bonus); 
  } 
} 
  
addCustomer({id: 101, name: "Rajesh Gunasundaram"}); 

Here, the bonus property has been defined as an optional property by concatenating a question mark (?) at the end of the name property.

Function type interfaces

We just saw how to define properties in interfaces. Similarly, we can also define function types in interfaces. We can define function types in interfaces by just giving the signature of the function with the return type. Note that, in the following code snippet, we have not added the function name:

interface AddCustomerFunc { 
  (firstName: string, lastName: string): string; 
} 

Now, we have AddCustomerFunc ready. Let's define an interface variable called AddCustomerFunc and assign a function of the same signature to it as follows:

var addCustomer: AddCustomerFunc; 
addCustomer = function(firstName: string, lastName: string) { 
  console.log('Full Name: ' + firstName + ' ' + lastName); 
  return firstName + ' ' + lastName; 
} 

The parameter name in the function signature can vary, but not the data type. For example, we can alter the fn and ln function parameters of the string type as follows:

addCustomer = function(fn: string, ln: string) { 
  console.log('Full Name: ' + fn + ' ' + ln); 
} 

So, if we change the data type of the parameter or the return type of the function here, the compiler will throw an error about the parameter not matching or the return type not matching with the AddCustomerFunc interface.

Array type interfaces

We can also define an interface for array types. We can specify the data type for the index array and the data type to the array item as follows:

interface CutomerNameArray { 
  [index: number]: string; 
} 
  
var customerNameList: CutomerNameArray; 
customerNameList = ["Rajesh", "Gunasundaram"]; 
 

TypeScript supports two types of index: number and string. This array type interface also stipulates that the return type of the array should match the declaration.

Class type interfaces

Class type interfaces define the contract for classes. A class that implements an interface should meet the requirement of the interface:

interface CustomerInterface { 
    id: number; 
    firstName: string; 
    lastName: string; 
    addCustomer(firstName: string, lastName: string); 
    getCustomer(id: number): Customer; 
} 
  
class Customer implements CustomerInterface { 
    id: number; 
    firstName: string; 
    lastName: string; 
    constructor() { } 
    addCustomer(firstName: string, lastName: string) { 
        // code to add customer 
   } 
    getCustomer(id: number): Customer { 
        return this; 
    } 
} 

The class type interface only deals with public members of the class. So, it is not possible to add private members to the interface.

Extending interfaces

Interfaces can be extended. Extending an interface makes it share the properties of another interface, as follows:

interface Manager { 
    hasPower: boolean; 
} 
  
interface Employee extends Manager { 
    name: string; 
} 
  
var employee = <Employee>{}; 
employee.name = "Rajesh Gunasundaram"; 
employee.hasPower = true; 

Here, the Employee interface extends the Manager interface and shares its hasPower with the Employee interface.

Hybrid type interfaces

Hybrid type interfaces are used when we want to use the object both as a function and an object. We can call an object like a function if it implements a hybrid type interface, or we can use it as an object and access its properties. This type of interface enables you to use an interface as an object and a function, as follows:

interface Customer { 
    (name: string); 
    name: string; 
    deleteCustomer(id: number): void; 
} 
  
var c: Customer; 
c('Rajesh Gunasundaram'); 
c.name = 'Rajesh Gunasundaram'; 
c.deleteCustomer(101); 

Inheritance

Inheritance is the concept of inheriting behaviors from another class or object. It helps to achieve code reusability and build a hierarchy in relationships of classes or objects. Also, inheritance helps you to cast similar classes.

JavaScript, by targeting ES5, doesn't support classes, and so class inheritance is impossible to implement. However, we can implement prototype inheritance instead of class inheritance. Let's explore inheritance in ES5 with examples.

First, create a function named Animal as follows:

var Animal = function() { 
  
    this.sleep = function() { 
       console.log('sleeping'); 
   } 
  
    this.eat = function() { 
       console.log('eating'); 
   } 
} 

Here, we created a function named Animal with two methods: sleep and eat. Now, let's extend this Animal function using the prototype as follows:

Animal.prototype.bark = function() { 
    console.log('barking'); 
} 

Now, we can create an instance of Animal and call the extended function, bark, as follows:

var a = new Animal(); 
a.bark(); 

We can use the Object.Create method to clone a prototype of the parent and create a child object. Then, we can extend the child object by adding methods. Let's create an object named Dog and inherit it from Animal:

var Dog = function() { 
    this.bark = new function() { 
       console.log('barking'); 
   } 
} 

Now, let's clone the prototype of Animal and inherit all the behavior in the Dog function. Then, we can call the Animal method using the Dog instance, as follows:

Dog.prototype = Object.create(animal.prototype); 
var d = new Dog(); 
d.sleep(); 
d.eat(); 

Inheritance in TypeScript

We just saw how to implement an inheritance in JavaScript using a prototype. Now, we will see how an inheritance can be implemented in TypeScript, which is basically ES6 inheritance.

In TypeScript, similar to extending interfaces, we can also extend a class by inheriting another class, as follows:

class SimpleCalculator { 
   z: number; 
    constructor() { } 
   addition(x: number, y: number) { 
        this.z = this.x + this.y; 
   } 
    subtraction(x: number, y: number) { 
        this.z = this.x - this.y; 
   } 
} 
  
class ComplexCalculator extends SimpleCalculator { 
    constructor() { super(); } 
   multiplication(x: number, y: number) { 
        this.z = x * y; 
   } 
    division(x: number, y: number) { 
        this.z = x / y; 
   } 
} 
var calculator = new ComplexCalculator(); 
calculator.addition(10, 20); 
calculator.Substraction(20, 10); 
calculator.multiplication(10, 20); 
calculator.division(20, 10); 

Here, we are able to access the methods of SimpleCalculator using the instance of ComplexCalculator as it extends SimpleCalculator.

Private and public modifiers

In TypeScript, all members in a class are public by default. We have to add the private keyword explicitly to control the visibility of the members, and this useful feature is not available in JavaScript:

class SimpleCalculator { 
    private x: number; 
    private y: number; 
    z: number; 
    constructor(x: number, y: number) { 
       this.x = x; 
       this.y = y; 
    } 
    addition() { 
        this.z = this.x + this.y; 
   } 
   subtraction() { 
        this.z = this.x - this.y; 
   } 
} 
  
class ComplexCalculator { 
    z: number; 
    constructor(private x: number, private y: number) { } 
    multiplication() { 
       this.z = this.x * this.y;  
   } 
    division() { 
        this.z = this.x / this.y; 
   } 
} 

Note that in the SimpleCalculator class, we defined x and y as private properties, which will not be visible outside the class. In ComplexCalculator, we defined x and y using parameter properties. These Parameter properties will enable us to create and initialize a member in one statement. Here, x and y are created and initialized in the constructor itself without writing any further statements inside it.

Accessors

We can also implement getters and setters to the properties to control accessing them from the client. We can intercept a process before setting a value to a property variable or before getting a value of the property variable:

var updateCustomerNameAllowed = true; 
class Customer { 
    private _name: string; 
    get name: string { 
          return this._name; 
   } 
    set name(newName: string) { 
          if (updateCustomerNameAllowed == true) { 
               this._name = newName; 
          } 
          else { 
               alert("Error: Updating Customer name not allowed!"); 
          } 
   } 
} 

Here, the setter for the name property ensures that the customer name can be updated. Otherwise, it shows an alert message to the effect that it is not possible.

Static properties

These properties are not instance-specific and are accessed by a class name instead of using the this keyword:

class Customer { 
     static bonusPercentage = 20; 
     constructor(public salary: number) {  } 
 
   calculateBonus() { 
          return this.salary * Customer.bonusPercentage/100; 
     } 
} 
var customer = new Customer(10000); 
var bonus = customer.calculateBonus(); 

Here, we declared a static variable called bonusPercentage that is accessed using the class name Customer in the calculateBonus method. This bonusPercentage property is not instance-specific.

Modules

JavaScript is a powerful and dynamic language. With dynamic programming in JavaScript, we need to structure and organize the code so that it will make its maintainability easier and also enable us to easily locate the code for a specific functionality. We can organize code by applying a modular pattern. Code can be separated into various modules, and relevant code can be put in each module.

TypeScript made it easier to implement modular programming using the module keyword. Modules enable you to control the scope of variables, code reusability, and encapsulation. TypeScript supports two types of module: internal and external modules.

Namespaces

We can create namespaces in TypeScript using the namespace keyword as follows. All the classes defined under namespace will be scoped under this namespace and will not be attached to the global scope:

namespace Inventory { 
      class Product { 
             constructor (public name: string, public quantity:  
                  number) {   } 
      } 
      // product is accessible 
      var p = new Product('mobile', 101); 
} 
 
// Product class is not accessible outside namespace 
var p = new Inventory.Product('mobile', 101); 

To make the Product class available for access outside the namespace, we need to add an export keyword when defining the Product class, as follows:

module Inventory { 
      export class Product { 
             constructor (public name: string, public quantity: number) {   } 
      } 
} 
 
// Product class is now accessible outside namespace 
var p = new Inventory.Product('mobile', 101); 

We can also share namespaces across files by adding a reference statement at the beginning of the code in the referring files, as follows:

/// <reference path="Inventory.ts" /> 

Modules

TypeScript also supports modules As we deal with a large number of external JavaScript libraries, this modularity will really help us organize our code. Using the import statement, we can import modules as follows:

Import { inv } from "./Inventory"; 
var p = new inv.Product('mobile', 101); 

Here, we just imported the previously created module, Inventory, created an instance of Product and assigned it to the variable p.

Functions

JavaScript, which follows the ES5 specs, does not support classes and modules. However, we tried to scope variables and modularity using functional programming in JavaScript. Functions are the building blocks of an application in JavaScript.

Though TypeScript supports classes and modules, functions play a key role in defining a specific logic. We can define both named functions and anonymous functions in JavaScript as follows:

//Named function 
function multiply(a, b) { 
    return a * b; 
} 
 
//Anonymous function 
var result = function(a, b) { return a * b; }; 

In TypeScript, we define functions with the type of the parameters and the return type using function arrow notation, which is also supported in ES6, as follows:

var multiply:(a: number, b: number) => number = 
          function(a: number, b: number): number { return a * b; }; 

Optional and default parameters

Say, for example, we have a function with three parameters, and sometimes we may only pass values for the first two parameters in the function. In TypeScript, we can handle such scenarios using the optional parameter. We can define the first two parameters as normal and the third parameter as optional, as given in the following code snippet:

function CustomerName(firstName: string, lastName: string, middleName?: string) { 
    if (middleName) 
        return firstName + " " + middleName + " " + lastName; 
    else 
        return firstName + " " + lastName; 
} 
//ignored optional parameter middleName 
var customer1 = customerName("Rajesh", "Gunasundaram"); 
//error, supplied too many parameters 
var customer2 = customerName("Scott", "Tiger", "Lion", "King");  
//supplied values for all 
var customer3 = customerName("Scott", "Tiger", "Lion");  

Here, middleName is the optional parameter, and it can be ignored when calling the function.

Now, let's see how to set default parameters in a function. If a value is not supplied to a parameter in the function, we can define it to take the default value that is configured:

function CustomerName(firstName: string, lastName: string, middleName: 
string = 'No Middle Name') { if (middleName) return firstName + " " + middleName + " " + lastName; else return firstName + " " + lastName; }

Here, middleName is the default parameter that will have No Middle Name by default if the value is not supplied by the caller.

Rest parameter

Using the rest parameter, you can pass an array of values to the function. This can be used in scenarios where you are not sure about how many values will be supplied to the function:

function clientName(firstClient: string, ...restOfClient: string[]) { 
   console.log(firstClient + " " + restOfClient.join(" ")); 
} 
clientName ("Scott", "Steve", "Bill", "Sergey", "Larry"); 

Here, note that the restOfClient rest parameter is prefixed with an ellipsis (...), and it can hold an array of strings. In the caller of the function, only the value of the first parameter that is supplied will be assigned to the firstClient parameter, and the remaining values will be assigned to restOfClient as array values.

Generics

Generics are very useful for developing reusable components that can work against any data type. So, the client that consumes this component will decide what type of data it should act upon. Let's create a simple function that returns whatever data is passed to it:

function returnNumberReceived(arg: number): number { 
    return arg; 
} 
unction returnStringReceived(arg: string): string { return arg; }

As you can see, we need individual methods to process each data type. We can implement them in a single function using the any data type, as follows:

function returnAnythingReceived (arg: any): any { 
    return arg; 
} 

This is similar to generics. However, we don't have control over the return type. If we pass a number and we can't predict whether the number will be returned or not by the function, the return type can be of any type.

Generics offers a special variable of type T. Applying this type to the function as follows enables the client to pass the data type they would like this function to process:

function returnWhatReceived<T>(arg: T): T { 
    return arg; 
} 

So, the client can call this function for various data types as follows:

var stringOutput = returnWhatReceived<string>("return this");  
// type of output will be 'string' 
var numberOutput = returnWhatReceived<number>(101);  
// type of output will be number 
 
Note that the data type to be processed is passed by wrapping it in angle brackets (<>) in the function call.

Generic interfaces

We can also define generic interfaces using the T type variable, as follows:

interface GenericFunc<T> { 
    (arg: T): T; 
} 
function func<T>(arg: T): T { 
    return arg; 
} 
var myFunc: GenericFunc<number> = func; 

Here, we defined a generic interface and the myFunc variable of the GenericFunc type, passing the number data type for the T type variable. Then, this variable is assigned with a function named func.

Generic classes

Similar to generic interfaces, we can also define generic classes. We define classes with a generic type in angle brackets (<>) as follows:

class GenericClass<T> { 
    add: (a: T, b: T) => T; 
}

var myGenericClass = new GenericClass<number>(); 
myGenericClass.add = function(a, b) { return a + b; }; 

Here, the generic class is instantiated by passing the generic data type as number. So, the add function will process and add two variables of type number passed as parameters.

Decorators

Decorators enable us to extend a class or object by adding behaviors without modifying code. Decorators wrap the class with extra functionality. Decorators can be attached to a class, property, method, parameter, and accessor. In ECMAScript 2016, decorators are proposed to modify the behavior of a class. Decorators are prefixed with the @ symbol and a decorator name that resolves to a function called at runtime.

The following code snippet shows the authorize function, and it can be used as the @authorize decorator on any other class:

function authorize(target) { 
    // check the authorization of the use to access the "target" 
} 

Class decorators

Class decorators are declared above the class declaration. Class decorators can observe, modify, and replace a class' definition that it is decorated by applying to the constructor of that class. The signature of ClassDecorator in TypeScript is as follows:

declare type ClassDecorator = <TFunction extends Function>(target:  
      TFunction) => TFunction | void; 

Consider a Customer class; we would like that class to be frozen. Its existing properties should not be removed or new properties should not be added.

We can create a separate class that can take any object and freeze it. We can then decorate the customer class with @freezed to prevent adding new properties or removing the existing properties from the class:

@freezed 
class Customer { 
  
  public firstName: string; 
  public lastName: string; 
  
  constructor(firstName : string, lastName : string) { 
    this.firstName = firstName; 
    this.lastName = lastName; 
  } 
} 

The preceding class takes four arguments in the firstname and lastname constructors. The following are the code snippets of the function written for the @freezed decorator:

function freezed(target: any) { 
    Object.freeze(target); 
} 

Here, the freezed decorator takes target, which is the Customer class that is being decorated, and freezes it when it gets executed.

Method decorators

Method decorators are declared before the method declaration. This decorator is used to modify, observe, or replace a method definition and is applied to the property descriptor for the method. The following code snippet shows a simple class with an applied method decorator:

class Hello { 
    @logging 
    increment(n: number) { 
        return n++; 
   } 
} 
 

The Hello class has the increment method that increments a number supplied to its parameter. Note that the increment method is decorated with the @logging decorator to log input and output of the increment method. The following is the code snippet of the logging function:

function logging(target: Object, key: string, value: any) { 
    
        value.value = function (...args: any[]) { 
            var result = value.apply(this, args); 
            console.log(JSON.stringify(args)); 
            return result; 
        } 
    }; 
} 

The method decorator function takes three arguments: target, key, and value. target holds the method that is being decorated; key holds the name of the method being decorated; and value is the property descriptor of the specified property if it exists on the object.

The logging method gets invoked when the increment method is called and it logs the value to the console.

Accessor decorators

Accessor decorators are prefixed before the accessor declaration. These decorators are used to observe, modify, or replace an accessor definition and are applied to the property descriptor. The following code snippet shows a simple class with the applied accessor decorator applied:

class Customer { 
  private _firstname: string; 
  private _lastname: string; 
  
  constructor(firstname: string, lastname: string) { 
        this._firstname = firstname; 
        this._lastname = lastname; 
  } 
  
  @logging(false) 
  get firstname() { return this._firstname; } 
  
  @logging(false) 
  get lastname() { return this._lastname; } 
} 

In this class, we decorate the get accessor of firstname and lastname with @logging and pass boolean to enable or disable logging. The following code snippet shows the function for the @logging decorator:

function logging(value: boolean) { 
    return function (target: any, propertyKey: string, descriptor: 
PropertyDescriptor) { descriptor.logging = value; }; }

The logging function sets the Boolean value to the logging property descriptor.

Property decorators

Property decorators are prefixed to property declarations. They actually redefine the property decorated by adding extra behavior. The signature of PropertyDecorator in the TypeScript source code is as follows:

declare type PropertyDecorator = (target: Object, propertyKey: string | 
symbol) => void;

The following is a code snippet of a class with a property decorator applied to a property:

class Customer { 
  @hashify 
  public firstname: string; 
  public lastname: string; 
  
  constructor(firstname : string, lastname : string) { 
    this.firstname = firstname; 
    this.lastname = lastname; 
  } 
} 

In this code, the firstname property is decorated with the @hashify property decorator. Now, we will see the code snippet of the @hashify property decorator function:

function hashify(target: any, key: string) { 
  var _value = this[key]; 
  
  var getter = function () { 
        return '#' + _value; 
  }; 
  
  var setter = function (newValue) { 
    _value = newValue; 
  }; 
  
  if (delete this[key]) { 
    Object.defineProperty(target, key, { 
      get: getter, 
      set: setter, 
      enumerable: true, 
      configurable: true 
    }); 
  } 
} 

The _value holds the value of the property that is being decorated. Both getter and setter functions will have access to the variable _value and here we can manipulate the _value by adding extra behaviors. I have concatenated # in the getter to return a hash-tagged firstname. Then we delete the original property from the class prototype using the delete operator. A new property will be created with the original property name with the extra behavior.

Parameter decorators

Parameter decorators are prefixed to parameter declarations, and they are applied to a function for a class constructor or a method declaration. The signature of ParameterDecorator is as follows:

declare type ParameterDecorator = (target: Object, propertyKey:  
   string | symbol, parameterIndex: number) => void; 

Now, let's define the Customer class and use a parameter decorator to decorate a parameter in order to make it required and validate whether the value has been served:

class Customer { 
    constructor() {  } 
  
    getName(@logging name: string) { 
        return name; 
   } 
} 

Here, the name parameter has been decorated with @logging. The parameter decorator implicitly takes three inputs, namely prototype of the class that has this decorator, the name of the method that has this decorator, and the index of the parameter that is being decorated. The logging function implementation of the parameter decorator is as follows:

function logging(target: any, key : string, index : number) { 
  
  console.log(target); 
  console.log(key); 
  console.log(index); 
} 

Here, target is the class that has the decorator, key is the function name, and index contains the parameter index. This code just logs target, key, and index to the console.