16

Background

Trying to get into the spirit of TypeScript, I am writing fully typed signatures in my Components and Services, which extends to my custom validation functions for angular2 forms.

I know that I can overload a function signature, but this requires that the parameters are different for each return type because tsc compiles each signature to a separate function:

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any { /*common logic*/ };

I also know that I can return a single type (like a Promise) which can itself be of multiple sub-types:

private active(): Promise<void|null> { ... }

However, in the context of angular2 custom form validators, a single signature (one parameter of type FormControl) can return two distinct types: an Object with form errors, or null to indicate the control has no errors.

This, obviously, does not work:

private lowercaseValidator(c: FormControl): null;
private lowercaseValidator(c: FormControl): Object {
    return /[a-z]/g.test(c.value) ? null : { lowercase: this.validationMessages.lowercase };
}

Nor do

private lowercaseValidator(c: FormControl): null|Object {...}
private lowercaseValidator(c: FormControl): <null|Object> {...}

(Interestingly, I get the following errors, rather than something more informative:

error TS1110: Type expected.
error TS1005: ':' expected.
error TS1005: ',' expected.
error TS1128: Declaration or statement expected.

)

TL;DR

Am I left simply using

private lowercaseValidator(c: FormControl): any { ... }

which would seem to negate the advantage of having type signatures?

More generally, looking forward to ES6

While this question is inspired by angular2 forms validators, which are handled directly by the framework, so you might not care about declaring the return type, it is still generally-applicable, especially given ES6 constructs like function (a, b, ...others) {}

Perhaps it's simply better practice to avoid writing functions that can return multiple types, but it is rather idiomatic, owing to JavaScript's dynamic nature.

References

Community
  • 1
  • 1
msanford
  • 10,127
  • 8
  • 56
  • 83
  • 1
    Why do you need the null there? It's not a separate type and Object can be null. – Sami Kuhmonen Oct 25 '16 at 14:20
  • 1
    (1) Don't use `Object`, use `any` instead. (2) This: `private lowercaseValidator(c: FormControl): null|Object {...}` should work. – Nitzan Tomer Oct 25 '16 at 14:22
  • 1
    `null | Object` seems to work on [the playground](https://www.typescriptlang.org/play/#src=type%20FormControl%20%3D%20any%3B%0D%0A%0D%0Aclass%20Thing%20%7B%0D%0A%20%20%20%20private%20lowercaseValidator(c%3A%20FormControl)%3A%20null%20%7C%20Object%20%7B%0D%0A%20%20%20%20%20%20%20%20return%20null%3B%0D%0A%20%20%20%20%7D%0D%0A%7D) – Sean Vieira Oct 25 '16 at 14:22
  • @SamiKuhmonen Kickself: I didn't consider that. I guess I'm over-thinking the specific need to reinforce the fact that it will return "nothing" or "something". That said, the middle part of the question is still (perhaps) applicable. – msanford Oct 25 '16 at 14:28
  • @NitzanTomer That is definitely an option, but if I'm going to do that, why bother giving a type declaration? "You're over-asserting things, just don't worry about it" might be a reasonable answer :) – msanford Oct 25 '16 at 15:28
  • 1
    That's not what I meant, using `Object` is almost like using `any`, but: "The any type is a powerful way to work with existing JavaScript, allowing you to gradually opt-in and opt-out of type-checking during compilation. You might expect Object to play a similar role, as it does in other languages. But variables of type Object only allow you to assign any value to them - you can’t call arbitrary methods on them, even ones that actually exist" (https://www.typescriptlang.org/docs/handbook/basic-types.html#any) – Nitzan Tomer Oct 25 '16 at 15:35
  • @NitzanTomer Thanks for the clarification! – msanford Oct 25 '16 at 15:40

2 Answers2

14

Ok, this is the right way if you want to have proper types:

type CustomType = { lowercase: TypeOfTheProperty };
// Sorry I cannot deduce type of this.validationMessages.lowercase,
// I would have to see the whole class. I guess it's something
// like Array<string> or string, but I'm not Angular guy, just guessing.

private lowercaseValidator(c: FormControl): CustomType | null {
    return /[a-z]/g.test(c.value) ? null : { lowercase: this.validationMessages.lowercase };
}

More general example

type CustomType = { lowercase: Array<string> };

class A {
      private obj: Array<string>;

      constructor() {
            this.obj = Array<string>();
            this.obj.push("apple");
            this.obj.push("bread");
      }

      public testMethod(b: boolean): CustomType | null {
            return b ? null : { lowercase: this.obj };
      }
}

let a = new A();
let customObj: CustomType | null = a.testMethod(false);
// If you're using strictNullChecks, you must write both CustomType and null
// If you're not CustomType is sufficiant
Erik Cupal
  • 2,029
  • 1
  • 14
  • 20
  • I have a problem using a method that returns multiple types. If i have my own method that returns CustomType1 from like this: `mymethod(): CustomType1 { return someMethodThatReturnsCustomType1AndNull(); }` I get an error. How to fix this? – HisDivineShadow Mar 22 '20 at 19:41
  • It's preferable to use `interface` over `type` unless you actually _need_ to use type. – Josh Habdas Feb 22 '21 at 05:08
3

To add to Erik's reply. In the last line of his second example, instead of redeclaring the type, you could use the "as" keyword.

let customObj = a.testMethod(false) as CustomType;

So basically, if you have a function that has multiple return types, you could specify which of the types should be assigned to a variable by using the "as" keyword.

type Person = { name: string; age: number | string };

const employees: Person[] = [
  { name: "John", age: 42},
  { name: "April", age: "N/A" }
];

const canTakeAlcohol = employees.filter(emp => {
  (emp.age as number) > 21;
});
Shonubi Korede
  • 398
  • 2
  • 4