Is there a way I can check a function parameter has an allowed value before runtime with Typescript? - javascript

I have an AngularJS service coded in Typescript. I call the functions of the service with a parameter like this setFalse('retrieve'):
class StateService implements IStateService {
state = [];
get = (property: string) => {
return this.state[property];
}
setFalse = (property: string) => {
this.state[property] = false;
}
setNull = (property: string) => {
this.state[property] = null;
}
setTrue = (property: string) => {
this.state[property] = true;
}
// more code here
}
Is there some way that I could remove the need for the quoted string 'retrieve', use a constant or check the values being used before run time?

1. Is there some way that I could remove the need for the quoted string 'retrieve'?
You could desugar the overload, and provide lots of different versions of the function.
//just to give the idea
function setFalse_retrieve(){ return setFalse('retrieve') }
The upside of doing this is that its really typesafe and there is no way to ever call setFalse with a bad parameter. The downside is that there is lots of boilerplate and you can't pass around a property value if you want to.
2. Use a constant?
Typescript has an enum feature for this:
enum Properties { retrieve, frobnicate };
You can now use Properties.retrieve instead of "retrieve" in your code and it will catch any typos in the enum name.
Properties.retriev; // Error: The property 'retriev' does not exist on value of type 'typeof Properties'.
Just be aware that Typescript makes the enum values be integers so you will need to convert them to strings when calling the Angular functions:
var enumProperty = Properties.retrieve; // 0
var strProperty = Properties[enumProperty]; // "retrieve"
The downside of the enum approach is that you can pass any number where an enum value is expected and the error will not be detected at runtime (do don't do that):
var x:Property = 10; // :(
3. Use a constant or check the values being used before run time
Typescript has function overloading on function constants but AFAIK, you can only use it to specialize return types depending on inputs, not to restrict your valid inputs to a set of constants. That is, you would still need to have a generic case that accepts any string, which is not what you want.

Related

specify the result of a function in javascript

Am not sure if functions in javascript are what we call methods in other programming languages.Methods in other programming languages can have their result specified just after the access-specifier like in C# for example i would do like
//method to return int
private int myinteger(){
int a=0;
return a;
}
//method to return string
private string mystring(){
string b="myreturn";
return b;
}
I just don't know how to do that with javascript functions, you think you can help me with a sample?Thank You very much :)
You don't need to provide the data types in javascript. The functions are pretty similar you just have to start it with the function keyword.
Also, we need to start the variables with const or let.
I use the console.log(myinteger()); below to log the value of the myinteger() function in the browser console. (Similar to c++'s cout)
//method to return int
function myinteger() {
const a = 0;
return a;
}
//method to return string
function mystring() {
const b = "myreturn";
return b;
}
console.log(myinteger());
console.log(mystring());
If you are someone who wants to use javascript but still want to assign the data type and many more thing then you can use TypeScript By Microsoft.
You cannot do this with javascript, but you still have two workarounds available:
Use eslint and use the https://eslint.org/docs/rules/consistent-return rule
Use typescript
Javascript has types for values (not variables)
so you can define a variable as
var name = "Hamond";
and to know it's type you have to use typeof
typeof name; // "string"
Side note: you can use let or const instead of var but let that be for another time.
so variables in javascript doesn't have types, values have.
You can add static typing using typescript so
var name: string = "Hamond";
and at dev time if you wanted to edit name and incorrectly deal with it as a non string type you will get an error
warning you immediately
name = 3; // error
name - 4; // error
// and so forth because `name` is of `string` type
so this type check is done at author or dev time and you don't have to wait until run time to get the error.
Why all the talk about variables and values?
Because Javascript function can return any value(even returning a variable is basically returning its value if it's scalar value or its reference if it's an object type)
so defining a function look like:
function doSomething(){
return 33;
}
notes:
no return type
can have no return statement(by default will return undefined)
with typescript
function doSomething(): number{
return 33;
}
typing problems solved at dev/write time
About function vs method:
I think developers in many times use these terms interchangeably, but in javascript we just have function, even a function defined inside a class in javascript is just a function. People like the name method when its defined inside some class.
references:
JS syntax
The Nature Of Functions

What javascript patterns are used to create single functions that accept different parameters types?

Working in JS for some time now, I noticed a common feature with some JS libraries (eg. jQuery, momentJS, Vue). They usually expose single function that can accept different parameters, both in terms of parameter type and in terms of number of parameters.
For example, let's say there is a foo function exposed by fooLib. I can use this same function in many ways such as:
import { foo } from './fooLib'
let a = foo('test') // pass string
let b = foo({ }) // pass object
let c = foo([]) // pass array
let d = foo('test', { }) // pass multiple parameters of different type
I've never written a JS library myself but sometimes I feel like it may be useful to create function like this in my app, although I am not aware what are the best practices in that case. I tried a few times to create something like this on the small scale by checking parameter types, but the code quickly got complex and unreadable.
So how is this accomplished in general? Are there some JS patterns for this type of programming?
To do this, you'll need to
(1) use typeof to check the types of the argument(s)
(2) In the function, define all possible arguments that may be passed (or use parameter rest syntax if the number of arguments is unlimited)
Something like:
const foo = (...args) => {
const filters = [];
for (const arg of args) {
if (typeof arg === 'string') filters.push(arg);
else if (Array.isArray(arg)) filters.push(...arg);
else if (arg && typeof arg === 'object') filters.push(Object.entries(arg));
}
console.log('foo doing something with:', filters);
};
foo('test') // pass string
foo({ someProp: 'someVal' }) // pass object
foo(['someArrayItem']) // pass array
foo('test', { anotherTestProp: 'anotherTestItem' }) // pass multiple parameters of different type
The logic can get a bit involved, but if the API is designed well, it doesn't have to be that difficult to implement or understand.

A Typescript syntax clarification

I am reading a code written in Typescript. I'm not sure if I understand it correctly:
export class MyClass<B> {
value: B;
constructor(value: B) {
this.value = value;
}
isMyClass(): this is MyClass<B> {
return true;
}
}
What does the <B> stand for? What does it represent, a Type? If so, what Type is it?
What is this is MyClass<B> in isMyClass(): this is MyClass<B>? Is it being evaluated for true or false? Why not to put this inside of the function itself then, something like this:
isMyClass() {
if (this is MyClass) {
return true;
}
else {
return false;
}
}
And I am not able to find the answer to a these questions.
What does the <B> stand for? What does it represent, a Type? If so, what Type is it?
That's a type parameter, also known as generics. See the TS handbook.
In languages like C# and Java, one of the main tools in the toolbox for creating reusable components is generics, that is, being able to create a component that can work over a variety of types rather than a single one. This allows users to consume these components and use their own types.
Whenever one calls a function or creates an instance, if the function or class is generic, you can "pass" a type parameter to it, similar to how arguments can be passed to a function or constructor. (The difference is that the type parameter, being a type, doesn't exist in the emitted JS - it's just there to help TS type-check).
If so, what Type is it?
It's whatever type the constructor parameter is called with.
const m = new MyClass(3);
will result in
constructor(value: B)
where value is 3, telling TypeScript that the resulting instance is a MyClass<number> - in other words, that its value property holds a number, a B.
What is this is MyClass<B> in isMyClass(): this is MyClass<B>? Is it being evaluated for true or false? Why not to put this inside of the function itself then, something like this:
The
isMyClass(): this is MyClass<B> {
is a type guard. If the method returns true, it tells TypeScript that the instance is of type MyClass<B>.
While you could do something like:
isMyClass() {
if (this instanceof MyClass) {
return true;
}
else {
return false;
}
}
That wouldn't allow TypeScript to understand that the type has been narrowed when isMyClass is called; it'll just return a boolean. In contrast, using the is will both return a Boolean and give TypeScript type information about what was called.

Cast object type to another type

I have class where I set type of range to IntervalRange
export class Test {range: IntervalRange;}
then in parent class I initialize the value:
export class TestInitializer {
Create(){
return <Test>{
range: IntervalRange.initialize(Database.GetByName('MONTH').id};
}
InitializeElement() {
let test = <Test>JSON.parse(JSON.stringify(configuration));
return test;
}
then in my other component I use this as:
#Input() range: IntervalRange;
However on function range.getRange();
I get: ERROR TypeError: this.range.getRange is not a function
Why is that? it says range is an Object, though it should be IntervalRange.
I tried writing as IntervalRange, <IntervalRange> range
nothing worked. How to fix that?
Update: let type = typeof(this.range); prints "object"
method:
ngOnChanges() {
if (this.range) {
let type = typeof(this.range);
let ddd = this.range.getRange(); //<----- this is where I get error
}
A typecast only casts the type. Typescript doesn't exist at runtime. Therefore if JSON.parse doesn't return a proper Test instance (which it won't since methods won't get serialized), it will fail at runtime. Instead of typecasting you probably want to build up a Test instance, and load the serialized data into it.
The TypeScript "cast" (<A>tmp or tmp as A) :
makes structural type compatibility checking → it is unavailable if the source data has type any.
has no runtime counterpart → TypeScript types can be compatible but runtime types can differ, for instance an object literal vs a real class instance.
When the data comes from an unsafe/external source, e.g. JSON.parse() or a WebAPI call, it's a DTO (data transfer object), with the any type. The TypeScript "cast" is unsafe too.
To ensure the cast operation, you can use a mapping function from the DTO to the "domain model" class. The key point is to return a real class instance.
Object.assign(new IntervalRange(), this.range) (that you mentioned in a comment) satisfies this point.
We can be even more stricter with a "field by field mapping": this.a = range.a; this.b = range.b; ...).
Both options are better encaspulated in a function: the class constructor, a static factory method in the class, an external function in the same module.

How should union of Objects with optional properties be defined with Flow?

I have a function that deals with two type of parameters: string and object. There are 3 different object structure expected. That makes up to 4 possibles types:
type URL = string;
type Item = {| href: string |};
type ItemList = {| results: Item[] |};
type Params = {| offset?: number, limit?: number |};
So the function's options type is:
type Options = URL | Item | ItemList | Params;
And here's the actual function:
// No properties of "Params" are required
// so if they're all omitted, Params === {}
function request(opts: Options = {}) {
if (typeof opts === 'string') {
return 'opts is an URL';
}
if (typeof opts.href === 'string') {
return 'opts is an item';
}
if (Array.isArray(opts.results)) {
return 'opts is a list of items';
}
// Three of the four types are caught
// we're left with "Params" which may or may not
// have a "offset" and "limit" property.
// Destructuring to undefined values is fine here.
// Still, flow complains about the type not being met.
const { offset, limit } = opts;
return 'opts are parameters';
}
Flow is complaining about a few things:
opts = {} throws an incompatibility error. Since no properties of Params are required, shouldn't empty objects match it too? Note that opts = { offset: undefined } clears the error.
Properties "offset" and "limit" are declared not found. Since none of them are required, shouldn't undefined be valid values? And thus destructuring fine?
To summarize my question:
How do you define a type that accepts different types of object, with one having no required properties?
Edit: run the flow code in your browser.
Check out Flowtype - making a sealed empty object for an answer to your first question.
For your second, the answer basically is that Flow does not fully support this sort of type refinement. Disjoint unions were designed for this use case, though you do have to add a discriminator property to all of your objects. Obviously, this will require some nontrivial changes to your code. It's up to you to decide if this is feasible.
If it's not feasible, the best thing to do is probably to just cast through any in this function, and make sure you provide a type annotation for the return value. It looks like it's small enough that it's easy for a human to reason about, so the benefit of typechecking here may not be worth the effort. Of course that's a judgement call that is best left to you.

Categories