Even though I work pretty much everyday with TypeScript there's something new I learn about frequently. I really enjoy working with it and try to get the most out of it whenever possible - and there's one thing for sure: TypeScript can do a lot of things.
So here are some things I've learned through time which helped me to improve my applications and - ultimately - become a better TypeScript developer.
Utility types
TypeScript provides some useful types right out of the box - they're called utility types and can be found here.
One of my personal favorites is the Readonly<T>
type makes all properties of T
readonly, making it easy to help you to maintain immutability.
Another handy type is Required<T>
which makes all properties required.
There's even more with things like Pick<T, K>
which creates a type of picked properties from another type, NonNullable<T>
which removes null
and undefined
from properties, Exclude<T, U>
which removes properties from a type and even more - go check them out, they're really useful.
Use unknown instead of any
A lot of times if you don't have exact type information you might just go for any
. And that was actually totally fine - until TypeScript 3.0 introduced the new unknown
type, the "type-safe counterpart of any
" (as described by TypeScript).
Just like the any
type you can assign whatever you want to the unknown
type - but you can't do anything with it like the any
type allows for:
const imUnknown: unknown = "hello there";
const imAny: any = "hello there";
imUnknown.toUpperCase(); // error: object is of type "unknown"
imAny.toUpperCase(); // totally fine
Hence unknown
prevents calling any method on it due to its unknown nature. Additionally assigning unknown
types to other types is not possible either:
let a: unknown = "hello world";
let b: string;
b = a; // error: a is unknown hence it's not possible to assign it to `string`
Often times the variable you just declared any
is actually unknown
- which prevents faulty assignments or calling methods on it without knowing what methods are provided by it.
See here for further information regarding the unknown
type.
Lookup types
Lookup types are a neat way to help you finding what you're looking for. Let's assume you've got an object with a dynamic number of properties and want to get a specific property out of it - it's highly likely to do it the following way:
const persons = {
john: { surname: 'Doe' },
bob: { surname: 'The builder' }
};
persons.john; // equals { surname: 'Doe' }
But there's one problem: what if the john
property does not exist? Your application is likely to tragically die - but that could have been prevented by TypeScript!
By implementing a very small helper we're able to prevent this:
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key]; // Inferred type is T[K]
}
By using this helper we can assure that the property exists when using it:
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key]; // Inferred type is T[K]
}
const persons = {
john: { surname: 'Doe' },
bob: { surname: 'The builder' }
};
getProperty(persons, 'bob');
getProperty(persons, 'john');
getProperty(persons, 'someoneelse'); // Error: Argument of type "someonelse" is not assignable to parameter of type "bob" | "john"
It may be common to take use of that within classes which makes the obj
param obsolete. It might be implemented as a getter for getting a specific item where we want to know which elements are available. In this case we're going to use a fairly uncommon syntax which:
Which would cause calling personList.getPerson('john')
to be perfectly fine, while personList.getPerson('jane')
would fail since there's no jane
key available.
Extending third-party libraries
If you've ever wanted to extend a third party library but were limited by the public API of that library module augmentation is what you're looking for. But when do you want that?
Let's say we've got an Angular application and we want to name our routes. The Angular Route
interface doesn't provide a name
property by default - that's were module augmentation comes in handy.
By creating a typings.d.ts
in the root of our Angular project we can simply tell TypeScript that we want to have an additional, optional property which stores the name:
Now we can simply add a name to our routes:
const routes: Routes = [
{
path: '',
name: 'theMightyDashboard',
component: DashboardComponent
},
];
Which - for example - can be used within our components by injecting ActivatedRoute
:
constructor(private route: ActivatedRoute) {
console.log(route.routeConfig.name); // logs `theMightyDashboard`!
}
ES types
Often times I see TypeScript code where most things are properly typed, except native ES elements. Think of something like Maps, Promises, Arrays and all that kind of stuff. But TypeScript provide types for these objects right out of the box;
Maps for example do provide generics to define the contents of our map. So instead of
const map = new Map();
map.set('name', 'John Doe');
We can improve this by specifying the types of our map key and value:
const map: Map<string, string> = new Map();
map.set('name', 'John Doe');
This prevents us from accidentally adding a type which is not allowed:
const map: Map<string, string> = new Map();
map.set('name', 123); // error: 123 is not a string
The same applies to Set
:
const set: Set<number> = new Set();
set.add(123); // Ok!
set.add('whatever'); // Type error
Or native arrays:
const elements: Array<{ name: string }> = [];
elements.push({ name: 'John Doe' }); // Ok!
elements.push('John Doe'); // Type error
If you want to take a look at all types available see the official typings for ES5 and ES6 (or all the other specs)
Anything else?
As said in the beginning TypeScript can do so many things that it can get difficult to stay up to date at sometimes. If there's something you really like about TypeScript which you don't see everyday please let me know in the comments!