Essential TypeScript | 2019

Essential TypeScript: From Beginner to Pro

 

Abstract


class Person { constructor(public id: string, public name: string, public city: string) { } getDetails(): string { return `${this.name}, ${this.getSpecificDetails()}`; } abstract getSpecificDetails(): string; }getSpecificDetails(): string; } class Employee extends Person { constructor(public readonly id: string, public name: string, private dept: string, public city: string) { super(id, name, city); } Figure 11-5. Defining an abstract method Chapter 11 ■ Working With Classes and interfaCes 258 getSpecificDetails() { return `works in ${this.dept}`; } } class Customer { constructor(public readonly id: string, public name: string, public city: string, public creditLimit: number) { } } let data: (Person | Customer)[] = [ new Employee( fvega , Fidel Vega , Sales , Paris ), new Customer( ajones , Alice Jones , London , 500)]; data.forEach(item => { if (item instanceof Person) { console.log(item.getDetails()); } else { console.log(`Customer: ${item.name}`); } }); In this listing, Employee extends the abstract Person class, but the Customer class does not. The instanceof operator can be used to identify any object instantiated from a class that extends the abstract class, which allows narrowing in the Person | Customer union used as the type for the array. The code in Listing 11-15 produces the following output: Fidel Vega, works in Sales Customer: Alice Jones Using Interfaces Interfaces are used to describe the shape of an object, which a class that implements the interface must conform to, as shown in Listing 11-16. ■ Note interfaces have a similar purpose to shape types, described in Chapter 10, and successive versions of typescript have eroded the differences between these two features, to the point where they can often be used interchangeably to achieve the same effect, especially when dealing with simple types. interfaces do have some useful features, however, and they provide a development experience that is more consistent with other languages, such as C#. Chapter 11 ■ Working With Classes and interfaCes 259 Listing 11-16. Using an Interface in the index.ts File in the src Folder interface Person { name: string; getDetails(): string; } class Employee implements Person { constructor(public readonly id: string, public name: string, private dept: string, public city: string) { // no statements required } getDetails() { return `${this.name} works in ${this.dept}`; } } class Customer implements Person { constructor(public readonly id: string, public name: string, public city: string, public creditLimit: number) { // no statements required } getDetails() { return `${this.name} has ${this.creditLimit} limit`; } } let data: Person[] = [ new Employee( fvega , Fidel Vega , Sales , Paris ), new Customer( ajones , Alice Jones , London , 500)]; data.forEach(item => console.log(item.getDetails())); Interfaces are defined by the interface keyword and contain the set of properties and methods that a class must provide in order to conform to the interface, as shown in Figure 11-6. Figure 11-6. Defining an interface Chapter 11 ■ Working With Classes and interfaCes 260 Unlike abstract classes, interfaces don’t implement methods or define a constructor and just define a shape. Interfaces are implemented by classes through the implements keyword, as shown in Figure 11-7. The Person interface defines a name property and a getDetails method, so the Employee and Customer classes must define the same property and method. These classes can define extra properties and methods, but they can only conform to the interface by providing name and getDetails. The interface can be used in type annotations, such as the array in the example. ... let data: Person[] = [ new Employee( fvega , Fidel Vega , Sales , Paris ), new Customer( ajones , Alice Jones , London , 500)]; ... The data array can contain any object created from a class that implements the Product array, although the function passed to the forEach method is able to access only the features defined by the interface unless objects are narrowed to a more specific type. The code in Listing 11-16 produces the following output: Fidel Vega works in Sales Alice Jones has 500 limit MERGING INTERFACE DECLARATIONS interfaces can be defined in multiple interface declarations, which are merged by the compiler to form a single interface. this is an odd feature—and one that i have yet to find useful in my own projects. the declarations must be made in the same code file, and they must all be exported (defined with the export keyword) or defined locally (defined without the export keyword). Implementing Multiple Interfaces A class can implement more than one interface, meaning it must define the methods and properties defined by all of them, as shown in Listing 11-17. Figure 11-7. Implementing an interface Chapter 11 ■ Working With Classes and interfaCes 261 Listing 11-17. Implementing Multiple Interfaces in the index.ts File in the src Folder interface Person { name: string; getDetails(): string; } interface DogOwner { dogName: string; getDogDetails(): string; } class Employee implements Person { constructor(public readonly id: string, public name: string, private dept: string, public city: string) { // no statements required } getDetails() { return `${this.name} works in ${this.dept}`; } } class Customer implements Person, DogOwner { constructor(public readonly id: string, public name: string, public city: string, public creditLimit: number, public dogName ) { // no statements required } getDetails() { return `${this.name} has ${this.creditLimit} limit`; } getDogDetails() { return `${this.name} has a dog named ${this.dogName}`; } } let alice = new Customer( ajones , Alice Jones , London , 500, Fido ); let dogOwners: DogOwner[] = [alice]; dogOwners.forEach(item => console.log(item.getDogDetails())); let data: Person[] = [new Employee( fvega , Fidel Vega , Sales , Paris ), alice]; data.forEach(item => console.log(item.getDetails())); Chapter 11 ■ Working With Classes and interfaCes 262 Interfaces are listed after the implements keyword, separated with commas. In the listing, the Customer class implements the Person and DogOwner interfaces, which means that the Person object assigned to the variable named alice can be added to the arrays typed for Person and DogOwner objects. The code in Listing 11-17 produces the following output: Alice Jones has a dog named Fido Fidel Vega works in Sales Alice Jones has 500 limit ■ Note a class can implement multiple interfaces only if there are no overlapping properties with conflicting types. for example, if the Person interface defined a string property named id and if the DogOwner interface defined a number property with the same name, the Customer class would not be able to implement both interfaces because there is no value that could be assigned to its id property that could represent both types. Extending Interfaces Interfaces can be extended, just like classes. The same basic approach is used, and the result is an interface that contains the properties and methods inherited from its parent interfaces, along with any new features that are defined, as shown in Listing 11-18. Listing 11-18. Extending an Interface in the index.ts File in the src Folder interface Person { name: string; getDetails(): string; } interface DogOwner extends Person { dogName: string; getDogDetails(): string; } class Employee implements Person { constructor(public readonly id: string, public name: string, private dept: string, public city: string) { // no statements required } getDetails() { return `${this.name} works in ${this.dept}`; } } Chapter 11 ■ Working With Classes and interfaCes 263 class Customer implements DogOwner { constructor(public readonly id: string, public name: string, public city: string, public creditLimit: number, public dogName ) { // no statements required } getDetails() { return `${this.name} has ${this.creditLimit} limit`; } getDogDetails() { return `${this.name} has a dog named ${this.dogName}`; } } let alice = new Customer( ajones , Alice Jones , London , 500, Fido ); let dogOwners: DogOwner[] = [alice]; dogOwners.forEach(item => console.log(item.getDogDetails())); let data: Person[] = [new Employee( fvega , Fidel Vega , Sales , Paris ), alice]; data.forEach(item => console.log(item.getDetails())); The extend keyword is used to extend an interface. In the listing, the DogOwner interface extends the Person interface, which means that classes that implement DogOwner must define the properties and methods from both interfaces. Objects created from the Customer class can be treated as both DogOwner and Person objects, since they always define the shapes required by each interface. The code in Listing 11-18 produces the following output: Alice Jones has a dog named Fido Fidel Vega works in Sales Alice Jones has 500 limit INTERFACES AND SHAPE TYPES as noted at the start of this section, shape types and interfaces can often be used interchangeably. Classes can, for example, use the implements keyword with a shape type to indicate they implement the properties in the shape, like this: ... type Person = { name: string; getDetails(): string; }; Chapter 11 ■ Working With Classes and interfaCes 264 class Employee implements Person { constructor(public readonly id: string, public name: string, private dept: string, public city: string) { // no statements required } getDetails() { return `${this.name} works in ${this.dept}`; } } ... this fragment of code is based on listing 11-18 and replaces the Person interface with a shape type that has the same properties. the Employee class uses the implements keyword to declare that it conforms to the Person shape. interfaces can also conform to shape types, using the extends keyword, like this: ... type NamedObject = { name: string; }; interface Person extends NamedObject { getDetails(): string; }; ... in this fragment of code, the Person interface inherits the name property from the NamedObject shape t

Volume None
Pages None
DOI 10.1007/978-1-4842-4979-6
Language English
Journal Essential TypeScript

Full Text