22

If i have something like

array1: string[] = ['foo', 'bar'];

and an interface like

export interface MyObject { name: string; }

how can I map array1 to another array of type MyObject?

array2 : MyObject[];
array2 = array1.map(s => ...);

I thought about something like

array2 = array1.map<MyObject>(s => new MyObject(...));
rst
  • 2,510
  • 4
  • 21
  • 47
  • That works only if there's a constructor *value* named `MyObject` that produces instances of *type* `MyObject`. Since `MyObject` is just an interface you could make one via an [object literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer) like `{name: s}`. Is this homework or something? Just wondering how much help we should provide here. – jcalz Jan 24 '19 at 17:15
  • Not homework, code improvement of an angular application I bought from a supplier (company) – rst Jan 24 '19 at 17:18
  • 5
    Then `array1.map(s => ({name: s}))` should work for you. (edit: surrounded expression with parentheses since js interprets `s => {...}` incorrectly here) – jcalz Jan 24 '19 at 17:20
  • No that doesn't, compiler says `void is not assignable to type`, but this here works apparently `map(s => ({name: s}))` – rst Jan 24 '19 at 17:22
  • my edit didn't make it to you in time, oh well – jcalz Jan 24 '19 at 17:23
  • How about you make a true answer, so I can accept that for others? – rst Jan 24 '19 at 17:24
  • Okay, if you insist! – jcalz Jan 24 '19 at 18:58

2 Answers2

43

This is usually where I digress into a long lecture about the difference between types and values in TypeScript, but here I'll try to make it short: since MyObject is only known to be a type and not a constructor value, you can't call new MyObject(). Instead, if you want to make a value of type MyObject, you can just use a plain old object of the right shape, such as via object literal notation:

const myObject: MyObject = {name: "Alice"}; // no error

So your map function can be the following:

const array2: MyObject[] = array1.map(s => ({name: s})); // no error

Simple, right?

Uh, well, please note as in the comments above that the parentheses around {name: s} are necessary, because for better or worse, JavaScript parsers interpret arrow functions of the form (x => {...}) as having a block body where the stuff inside the curly braces are statements. And when you interpret name: s as a statement it means that name is a label, and s is just a string expression statement with the optional semicolon omitted. Interpreted as a function body, {name: s} just evaluates s and doesn't return a defined value (it is void, which is essentially the same as undefined), which is why you get the following weird error if you leave out the parentheses:

let array2: MyObject[] = array1.map(s => { name: s }); // error!
// Type 'void[]' is not assignable to type 'MyObject[]'.
// at runtime, array2 will be [undefined, undefined] which is not what you wanted 

JavaScript parsing is a bit tricky this way. Adding the parentheses to make s => (...) fixes it. Now the stuff after the arrow can only be interpreted as a concise body... that is, a single expression. And interpreting ({name: s}) as an expression yields the object literal that we wanted.

Yay, I avoided writing a book about types and values and somehow ended up writing a different book about expressions and statements. Oh well.

jcalz
  • 264,269
  • 27
  • 359
  • 360
10

you can't call MyObject as constructor because it is interface and not class. So an option here you can just map array1 properties as MyObject properties and after that change type to MyObject[] array with as

const array1: string[] = ['foo', 'bar'];

export interface MyObject { name: string; }

let array2 : MyObject[];

array2 = array1.map(v => { return {name: v}; }) as MyObject[];

or another option, you can also create MyObject class which will implement IMyObject interface and you can call constructor. Here is an example

const array1: string[] = ['foo', 'bar'];

export interface IMyObject { name: string; }

class MyObject implements IMyObject {
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

let array2 : IMyObject[];

array2 = array1.map(v => new MyObject(v)) as IMyObject[];
Artyom Amiryan
  • 2,846
  • 1
  • 10
  • 22