33

We are using simple function declaration quite a lot where function takes either single object or array of objects of some type.

Simple declaration is:

interface ISomeInterface {
    name: string;
}

class SomeClass {
    public names: ISomeInterface[] = [];

    public addNames(names: ISomeInterface | ISomeInterface[]): void {
        names = (!Array.isArray(names)) ? [names] : names;
        this.names = this.names.concat(names);
    }    
}

But TypeScript throws "type is not assignable" error.

Is there better way of doing this? Obviously we could have two separate functions, but I think handling single vs multiple this way is quite fine.

1
  • 6
    Protip: instead of isArray, use names = [].concat(names). 43% faster on chrome. Commented Sep 29, 2016 at 10:07

4 Answers 4

35

You can make it easier

 addNames(names: ISomeInterface | ISomeInterface[]): void {
        this.names = this.names.concat(names);
 } 

From MDN

The concat() method returns a new array comprised of the array on which it is called joined with the array(s) and/or value(s) provided as arguments.

Sign up to request clarification or add additional context in comments.

7 Comments

Great example of how I am over-engineering something instead of properly reading specs... thanks! I will keep the other answer as accepted though since it has a bit of extra info about TS that people seem not to know and it gives you more flexibility as to how pass the parameters
Actually, this still compiles into error: Error:(26, 46) TS2345: Argument of type 'ISomeInterface | ISomeInterface[]' is not assignable to parameter of type 'ISomeInterface'. Type 'ISomeInterface[]' is not assignable to type 'ISomeInterface'.
@Tom I've updated tsc to Version 1.9.0-dev.20160302 now it's working for me
Cool, I have still 1.8.7 but im gona believe you :) actually im gonna accept your answer I think its better considering the ... that i mentioned in the other answer
I had the same question and I later found out a different syntax of doing this addNames(names: ISomeInterface | Array<ISomeInterface>) just saying that this too works..
|
23

You could also use the rest parameter:

interface ISomeInterface {
    name: string;
}

class SomeClass {
    public names: ISomeInterface[] = []; // create an instance if applicable.

    addNames(...names: ISomeInterface[]): void {
        // the names argument will always be an array
        this.names = this.names.concat(names);
    }
}

You can call it like:

addNames(name1); // just pass one
addNames(name1, name2, name3); // pass more comma separated
addNames(...[name1, name2, name3]); // pass an array.

Please note that I removed the function keyword, because otherwise the this keyword inside the body block might lose scope depending on who's calling it.

9 Comments

Yes, beautiful, I did know about rest param but what I didn't know is that it compiles itself into params being always an array. thank you!
I didn't know rest parameters compiled down either. That is a neat solution!
@Tom I would also suggest instantiating the array as it would prevent you from doing null checks everywhere. public names: ISomeInterface[] = [];
Upvoted, this is the best solution IMO to pass "one or a list" of something to a function. Using a union type makes things unnecessarily cumbersome IMO.
@user2010955 You could do something like addNames(name: ISomeInterface, ...names: ISomeInterface[])
|
5

I think this is what you want

interface ISomeInterface {
    name: string;
}

class SomeClass {
    public names: ISomeInterface[];

    addNames(names: ISomeInterface | ISomeInterface[]): void {
        names = (names instanceof Array) ? names : [names];
        this.names = this.names.concat(<ISomeInterface[]>names)
    }    
}

You want to use instanceOf, not isArray.

1 Comment

Sorry, not what I was asking for and your code still gives the same error
2

The official way typescript handles this is with multiple function signatures, for example:

addNames(names: ISomeInterface): void;
addNames(names: ISomeInterface[]): void;
addNames(names: any): void {
    ...
}

You can see more information in the official handbook here

2 Comments

Yes thats what I thought, but then if you call addNames("Hello World") it doesn't throw any error, so basically you could just use :any declaration and it would be the same...
I think you just have to validate it yourself at that point. I am not sure how it would work but you could try replacing names: any with name?: ISomeInterface, names?: ISomeInterface[]. That should prevent passing a string but it would allow you to pass nothing or both also. Then inside the function use names || [name] and it would be pretty seamless.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.