Netanel Basal

Learn development with great articles

The Essentials of ReadT and WriteT Generics in Angular Signal Input Functions

--

Signal inputs, a notable feature introduced in version 17.1.0, have brought significant advancements in our coding practices. While I’ve previously delved into this feature extensively, there seems to be lingering confusion around the generics used with this function. This article aims to demystify the usage of ReadT and WriteT generics:

@Component({ })
export class UserProfileComponent {
👇 👇
skills = input<ReadT, WriteT>()
}

Understanding the Role of ReadT Generic

The ReadT generic plays a crucial role in inferring the signal type value during read operations within a component. For instance:

@Component({ })
export class UserProfileComponent {
// `ReadT` inferred as `string` from the initial value
id = input('');

// Explicitly setting `ReadT` as (string | number)
foo = input<string | number>(1)

constructor() {
effect(() => {
// `this.id()` type is string
console.log(this.id());

// `this.foo()` type is (string | number)
console.log(this.foo());
});
}
}

In scenarios where an initial value isn’t provided, and no explicit generic is passed, the type defaults to unknown. Conversely, if a generic is defined without an initial value, an undefined type is automatically included:

@Component({ })
export class UserProfileComponent {
// `ReadT` inferred as `unknown` because we didn't provide initial value
id = input();

// Explicilty sets ReadT as (string | number) with no initial value
foo = input<string | number>();

// `ReadT` inferred as `unknown`
bar = input.required();

// `ReadT` inferred as `string`
baz = input.required<string>();

constructor() {
effect(() => {
// `this.id()` type is `unknown`
console.log(this.id());

// `this.foo()` type is (string | number | undefined)
console.log(this.foo());

// `this.bar()` type is `unknown`
console.log(this.bar());

// `this.baz()` type is string
console.log(this.baz());
});
}
}

Exploring the WriteT Generic

The WriteT generic is primarily utilized when working with the transform option. It defines the types of values that can be written to the input. The transform function receives a WriteT type and is expected to return a ReadT type value.

Consider the following example:

@Component({ })
export class UserProfileComponent {
skills = input<string[], string | string[]>([], {
// `value` can be either `string` or `string[]`
transform(value) {
// Should return `string[]`
return Array.isArray(value) ? value : [value];
}
});
}

Here, our signal type is string[], but we can pass to the input both string and string[]. The type of the value passed to our transform function is string | string[], and it returns the ReadT type, which in this case is string[].

If you set only the ReadT generic without defining WriteT, you will encounter limitations:

@Component({ })
export class UserProfileComponent {
skills = input<string[]>([], {
// Results in a compilation error
transform(value) {
return Array.isArray(value) ? value : [value];
}
});
}

In this scenario, attempting to use the transform function will lead to a compilation error because WriteT is undefined.

Furthermore, when no generics are explicitly passed, the WriteT defaults to unknown:

@Component({ })
export class UserProfileComponent {
// `ReadT` = `number`, `WriteT` = `unknown`
foo = input(1, {
// `value` is `unknown`
transform(value) {
// Must return a number
return 2;
},
});
}

In cases where you use the required option without passing any generic, ReadT will be inferred from the return type of the transform function, and WriteT will be unknown:

@Component({ })
export class UserProfileComponent {
// `ReadT` = `number`, `WriteT` = `unknown`
foo = input.required({
// `value` is `unknown`
transform(value) {
return 1;
},
});
}

Always specify both generics when using the transform function. This ensures clarity and avoids type-related errors.

Follow me on Medium or Twitter to read more about Angular and JS!

--

--

Netanel Basal
Netanel Basal

Written by Netanel Basal

A FrontEnd Tech Lead, blogger, and open source maintainer. The founder of ngneat, husband and father.

Responses (1)