10 Advance TypeScript Concept
10 Advance TypeScript Concept
●
9913111
Niharika Goulikar
Posted on 14. Nov.
50
13
12
12
12
2.Generics with Type Constraints : Now let's limit the type T by defining it to
accept only strings and integers:
function func<T extends string | number>(value: T): T {
return value;
}
3.Generic Interfaces:
Interface generics are useful when you want to define contracts (shapes) for
objects, classes, or functions that work with a variety of types. They allow you
to define a blueprint that can adapt to different data types while keeping the
structure consistent.
// Generic interface with type parameters T and U
interface Repository<T, U> {
items: T[]; // Array of items of type T
add(item: T): void; // Function to add an item of type T
getById(id: U): T | undefined; // Function to get an item by ID of type U
}
// Usage
const userRepo = new UserRepository();
userRepo.add({ id: 1, name: "Alice" });
userRepo.add({ id: 2, name: "Bob" });
4.Generic Classes:: Use this when you want all the properties in your class
to adhere to the type specified by the generic parameter. This allows for
flexibility while ensuring that every property of the class matches the type
passed to the class.
interface User {
id: number;
name: string;
age: number;
}
constructor(user: T) {
this.id = user.id;
this.name = user.name;
this.age = user.age;
}
let x = { a: 1, b: 2, c: 3 };
getProperty(x, "a"); // Valid
getProperty(x, "d"); // Error: Argument of type '"d"' is not assignable to parameter of type '"a" |
"b" | "c"'.
interface MentalWellness {
mindfulnessPractice: boolean;
stressLevel: number; // Scale of 1 to 10
}
interface PhysicalWellness {
exerciseFrequency: string; // e.g., "daily", "weekly"
sleepDuration: number; // in hours
}
interface Productivity {
tasksCompleted: number;
focusLevel: number; // Scale of 1 to 10
}
// Combining all three areas into a single type using intersection types
type HealthyBody = MentalWellness & PhysicalWellness & Productivity;
8.Type Variance : This concept talks how subtype and supertype are related
to each other.
These are of two types:
Covariance: A subtype can be used where a supertype is expected.
Let's see an example for this:
class Vehicle { }
class Car extends Vehicle { }
// Covariant assignment
let car: Car = getCar();
let vehicle: Vehicle = getVehicle(); // This works because Car is a subtype of Vehicle
In the above example, Car has inherited properties from Vehicle class,so it's
absolutely valid to assign it to subtype where supertype is expected as
subtype would be having all the properties that a supertype has.
Contravariance: This is opposite of covariance.We use supertypes in places
where subType is expected to be.
class Vehicle {
startEngine() {
console.log("Vehicle engine starts");
}
}
class PhysicalWellness {
getPhysicalWellnessAdvice(): string {
return "Make sure to exercise daily for at least 30 minutes.";
}
}
console.log(healthAdvice.getHealthAdvice());
// Output: "Take time to meditate and relax your mind. Also, Make sure to exercise daily for at
least 30 minutes."
interface IPhysicalWellnessService {
getPhysicalWellnessAdvice(): string;
}
// Implementations of the services
class MentalWellness implements IMentalWellnessService {
getMentalWellnessAdvice(): string {
return "Take time to meditate and relax your mind.";
}
}
// Dependency injection
const mentalWellness: IMentalWellnessService = new MentalWellness();
const physicalWellness: IPhysicalWellnessService = new PhysicalWellness();
console.log(healthAdvice.getHealthAdvice());
// Output: "Take time to meditate and relax your mind. Also, Make sure to exercise daily for at
least 30 minutes."
In a tightly coupled scenario, if you have a stressLevel attribute in the
MentalWellness class today and decide to change it to something else
tomorrow, you would need to update all the places where it was used. This
can lead to a lot of refactoring and maintenance challenges.
However, with dependency injection and the use of interfaces, you can avoid
this problem. By passing the dependencies (such as the MentalWellness
service) through the constructor, the specific implementation details (like the
stressLevel attribute) are abstracted away behind the interface. This means
that changes to the attribute or class do not require modifications in the
dependent classes, as long as the interface remains the same. This approach
ensures that the code is loosely coupled, more maintainable, and easier to
test, as you’re injecting what’s needed at runtime without tightly coupling
components.