This lesson integrates key concepts and advanced ideas while emphasizing clean code practices in building a duck pond management application using TypeScript.
In this lesson, we integrate several key concepts and advanced ideas while emphasizing clean code practices. Elmer demonstrates how to build a complete duck pond management application using TypeScript features and best practices.
We begin by initializing an array to hold multiple duck objects. Explicitly typing the array is especially useful when data might come from various sources or when elements are conditionally created.Consider the example below:
Copy
Ask AI
// Duck pond array to store multiple Duck objects with explicit typingconst duckPond: PondDuck[] = [];// Create some Duck instances and add them to the duck pondconst daffy = new PondDuck('Daffy', 3, DuckType.Mallard, 'Black'); // Instance with inferred typingconst donald = new PondDuck('Donald', 5, DuckType.Pekin, 'White', 'Corn'); // Optional property providedconst howard = new PondDuck('Howard', 2, DuckType.Muscovy, 'Brown');// Adding ducks to the arrayduckPond.push(daffy, donald, howard);// ToDo:// Write a function to make a given list of ducks quack// Optional argument for the number of timesfunction makeAllDucksQuack(ducks: PondDuck[], times?: number): void {}
During compilation you might encounter errors related to the order of variable declarations. For instance, if the duck pond is declared before creating the duck instances:
Copy
Ask AI
[INFO] 21:37:57 Restarting: /root/code/index.ts has been modified[ERROR] 21:37:58 x Unable to compile TypeScript:index.ts(54,31): error TS2448: Block-scoped variable 'daffy' used before its declaration....
Defining the duck pond after creating the instances lets TypeScript infer the correct types. For example:
Copy
Ask AI
// Create some Duck instances firstconst daffy = new PondDuck('Daffy', 3, DuckType.Mallard, 'Black');const donald = new PondDuck('Donald', 5, DuckType.Pekin, 'White', 'Corn');const howard = new PondDuck('Howard', 2, DuckType.Muscovy, 'Brown');// Duck pond array inferred based on the created instancesconst duckPond = [daffy, donald, howard, "hi"]; // Inferred as a union typeduckPond.push("bye");// ToDo:// Write a function to make a given list of ducks quack// Optional argument for the number of timesfunction makeAllDucksQuack(ducks: PondDuck[], times?: number): void {}
This example highlights the impact of explicit typing versus type inference in TypeScript. By declaring the array after creating the instances, the inferred type should normally consist of PondDuck objects. However, adding a string element changes the type to a union (string | PondDuck), which can lead to type errors such as:
Copy
Ask AI
index.ts(64,10): error TS2339: Property 'p' does not exist on type 'PondDuck[]'.[ERROR] 21:38:46 Unable to compile TypeScript:index.ts(64,15): error TS2345: Argument of type 'string' is not assignable to parameter of type 'PondDuck'.
A cleaner approach leverages type inference without mixing unwanted types:
Copy
Ask AI
// Create Duck instancesconst daffy = new PondDuck('Daffy', 3, DuckType.Mallard, 'Black');const donald = new PondDuck('Donald', 5, DuckType.Pekin, 'White', 'Corn');const howard = new PondDuck('Howard', 2, DuckType.Muscovy, 'Brown');// Duck pond array inferred from provided instancesconst duckPond = [daffy, donald, howard];duckPond.push("bye"); // This will raise an error as "bye" is not assignable to type PondDuck// ToDo:// Write a function to make the given list of ducks quack// Optional argument for the number of timesfunction makeAllDucksQuack(ducks: PondDuck[], times?: number): void {}
Type inference helps reduce boilerplate, but be cautious when mixing types in arrays as it might lead to unexpected errors.
To have a list of ducks perform a quack action, use the array’s forEach method with an arrow function that calls each duck’s quack method. Review the refactored function below:
Copy
Ask AI
function makeAllDucksQuack(ducks: PondDuck[], times = 1): void { ducks.forEach((duck) => duck.quack(times));}// Example call to the function// makeAllDucksQuack(duckPond, 2);
When you run this function, you might see console outputs similar to:
Copy
Ask AI
Howard duck says: Quack!Howard the Brown Muscovy duck says: Quack!Daffy duck says: Quack!...
Within the PondDuck class, the quack method might be implemented as follows:
Copy
Ask AI
quack(times = 1): void { console.log(`${this.name} duck says: Quack!`); for (let i = 0; i < times; i++) { console.log(`${this.name} the ${this.color} ${this.type} duck says: Quack!`); }}
Next, we create a function to trigger the fly behavior for a duck based on its name. Note that the duckPond array is in the parent scope and referenced directly within the function. Although this approach works, passing data through function parameters is generally more maintainable.
Copy
Ask AI
function findDuckAndFly(name: string): void { const foundDuck = duckPond.find((duck) => duck.name === name); if (foundDuck) { foundDuck.fly(); } else { console.warn(`No duck named ${name} found in the pond.`); }}
Test this functionality with:
Copy
Ask AI
findDuckAndFly('Daffy'); // Should output: "Daffy starts flying!"
While makeAllDucksQuack is a pure function that receives all required data through parameters, findDuckAndFly depends on external state. For better code maintainability, pass necessary data explicitly.
Below is a consolidated version of the code tying everything together:
Copy
Ask AI
// Duck pond array to store multiple Duck objectsconst duckPond: PondDuck[] = [];// Create Duck instances and add them to the pondconst daffy = new PondDuck('Daffy', 3, DuckType.Mallard, 'Black');const donald = new PondDuck('Donald', 5, DuckType.Pekin, 'White', 'Corn');const howard = new PondDuck('Howard', 2, DuckType.Muscovy, 'Brown');duckPond.push(daffy, donald, howard);// Function to make all ducks in the pond quackfunction makeAllDucksQuack(ducks: PondDuck[], times = 1): void { ducks.forEach((duck) => duck.quack(times));}// Function to make a specific duck fly based on its name// Warns if the duck is not foundfunction findDuckAndFly(name: string): void { const foundDuck = duckPond.find((duck) => duck.name === name); if (foundDuck) { foundDuck.fly(); } else { console.warn(`No duck named ${name} found in the pond.`); }}// Usage examples:makeAllDucksQuack(duckPond, 3);findDuckAndFly('Donald');
Additional utility functions might include counting ducks by type:
Copy
Ask AI
function countDucksByType(type: DuckType): number { return duckPond.filter((duck) => duck.type === type).length;}const mallardCount: number = countDucksByType(DuckType.Mallard);console.log(`There are ${mallardCount} Mallard ducks in the pond.`);
And listing all ducks in the pond:
Copy
Ask AI
console.log('Ducks in the pond:', duckPond.map((duck) => duck.name).join(', '));