Reputation: 1083
INTRODUCTION
I'm using Angular 6.
Angular uses typescript, so you can type your function's arguments:
public fun1(aaa: number) {
console.log(aaa);
}
If I'll try to call fun1
with parameter of other type - I'll get an error:
public fun2() {
this.fun1([1, 2, 3]); // TS2345: Argument of type 'number[]' is not assignable to parameter of type 'number'
}
THE PROBLEM
Type checking works well if I can control the arguments in my files.
But maybe fun1
is called with some parameters which I get from backend.
Typescript doesn't workin in runtime, so it won't show any errors, the code of fun1
would just run with [1, 2, 3]
.
-- edited --
Backend response is not the only source of problem. Sometimes a library can change type of your data and you might not know about it. An example would be using Reactive Forms with a control which should be a number - it's converted to string when you edit the number.
QUESTION
Is there some common way to handle type checking in runtime?
I thought about something like:
public fun1(aaa: number) {
if (typeof aaa !== 'number') {
console.warn('wrong type');
} else {
console.log(aaa);
}
}
, but WebStorm then tells me typeof check is always false: 'aaa' always has type 'number'
.
Also putting something like this at the top of every function seems like adding a lot of unnecessary code.
Does someone have a good solution for this?
Upvotes: 5
Views: 4578
Reputation: 732
TypeScript - by design - does not provide runtime type information (see https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals, Non-goals, point 5).
If you feed TypeScript with some external data (maybe passed in data-attributes in HTML or fetched from REST API), TypeScript cannot know about its type. But there are some solutions. We'll have a look at two of them.
One approach you can take is to circumvent type system with as
operator. This is not a type safe approach (you introduce a hole in a type system) but it's (at least partially) in line with TypeScript goals (see a link above).
Let's pretend that fetchApiSync
function call returns just a simple javascript object {name: "John", age: 42}
and we have a function:
function introduceMe(person: Person) {
return `My name is ${person.name} and I'm ${person.age} years old.`
}
along with an interface
interface Person {
name: string;
age: number;
}
Now let's do the following:
const person: Person = fetchPersonFromApi(1);
.
Clearly, we would like fetchPersonFromApi
to return a value that matches Person
interface so that we can do:
const introduction = introduceMe(person);
To achieve it, we can do something like this:
function fetchPersonFromApi(id: number): Person {
// it's synchronous and without error handling just for brevity
const response: Person = fetchApiSync(`/persons/${id}`) as Person
return response;
}
It type checks as we did an explicit cast of whatever we get from an API to a Person when we used as Person
. It's "necessary" because fetchApiSync
has no way to know about the types of the values that it gets from an external API. Of course, we run into problems when instead of a value of type Person we get a value of some other type but there is nothing that we can do about it... or can we?
Another variant of this option is to use any
(or unknown
from TS 3) types. These two allow you to skip the type checks - value of type any
can be assigned to "any" type, including Person
. But errors can be hard to trace as you have an unchecked value inside your type system. Use it wisely!
Second approach that you can take is to use io-ts
library (https://github.com/gcanti/io-ts). It allows you to specify runtime types
and perform type checks on runtime (hence, it introduces an overhead of validating if a value matches a type). Moreover, you need to specify those types in slightly different manner (see documentation) so that they can exist on runtime. Good thing to note is that io-ts allows you to derive your pure-TypeScript type from io-ts type definitions. Using io-ts allows you to stay more type safe and protect your codebase from nasty casting or using any
type (or unknown
type from TypeScript 3). It does so at some cost - a part of which is non-negligible complexity.
Upvotes: 5