Create type extensions in Typescript
Create type extensions in Typescript
Typescript is a superset of JavaScript. It provides type safety on top of the JS libraries. Type safety is an important part of the development experience as it allows us to detect problems early thanks to the compiler preventing us from writing broken code.
JS being dynamic it is very easy to extend since anything is assumed to exist. In the context of extension methods, the only step needed is to add the method to the prototype of the class and we are done.
Typescript kept that flexibility but in order to provide type safety on top of it, extra steps are needed.
Today I would like to share how we can create extension methods in Typescript by extending existing types. This post will be composed by 2 parts:
1. Extending a core type
2. Extending a library type
1. Extending a core type
The first thing we need to do is create the function which will become the extension.
Here we will create a sumBy function to extend the Array core type.
function sumBy<T>(this: T[], selector: (x: T) => number) {
return this.reduce((pre, cur) => pre + selector(cur), 0);
}
We have a selector
which will select the property to sum by and we reduce the array by adding all selected properties.
Notice that we use this
parameter as the first parameter. It is to specify that this function is expected to be called on and instance of an array.
More about this
on Typescript documentation.(https://www.typescriptlang.org/docs/handbook/functions.html)
Next we can add the function to the array prototype.
Array.prototype.sumBy = sumBy;
What we notice here is that Typescript complains that sumBy does not exists. In order to fix this, we need to declare
the function for Array
.
interface Array<T> {
sumBy<T>(this: T[], selector: (x: T) => number): number;
}
Now we can use it like so:
const sum = data.sumBy(x => x.Amount);
Make sure to import the module where the extension is defined.
The complete extension code is as followed:
interface Array<T> {
sumBy<T>(this: T[], selector: (x: T) => number): number;
}
function sumBy<T>(this: T[], selector: (x: T) => number) {
return this.reduce((pre, cur) => pre + selector(cur), 0);
}
Array.prototype.sumBy = sumBy;
2. Extending a library type
There are times where it is necessary to extend types from external libraries.
For example if we need to extend the Observable type provided by rx/js, we will need to declare the extension in the right module on the original type. Because Observable is inside rxjs/Observable
, we can declare
is like so:
import { Observable } from 'rxjs/Observable';
declare module 'rxjs/Observable' {
interface Observable<T> {
isTrue(this: Observable<T>): Observable<T>;
}
}
function isTrue<T>(this: Observable<T>) {
return this.filter(v => !!v);
}
Observable.prototype.isTrue = isTrue;
Compared to Array, here we use declare module 'rxjs/Observable'
in order to add the extension to the right interface.
Conclusion
Today we saw how to define extension methods for core types and library types for Typescript. It is a convenient way to place reusable and easily accessible code. As usual, if you have any question, leave it here or hit me on Twitter @Kimserey_Lam. See you next time!
Great! This was the only solution I could find for this. You should write a StackOverflow Question + Answer :-)
ReplyDeleteDoesn't this solution still modify ("pollute") the prototype object? The nice thing about the C# version is that it's scoped to the file; External libraries can use similarly named extension functions and the won't collide like this solution would. I think to do this without polluting the prototype object would require compiler support. :(
ReplyDelete