25

I know in Typescript optional parameters can be marked by question mark.

However, the only way I found to actually instantiate the class with new keyword.

The thing is that based on Angular 2's starter "hero" tutorial classes are not instantiated via the new keyword and as far as I understood that's internally done by Angular.

For example I have this code:

models/users.ts

export class User {
    id: number;
    name: string; // I want this to be optional
}

models/mock-users.ts

import {User} from './user';

export var USERS: User[] = [
    {
       id: 1
       // no name (I wanted it to be optional for user with id 1)
    },
    {
       id: 2,
       name: "User 2"
    },        
]

services/user.service.ts

import {Injectable} from 'angular2/core';
import {USERS} from './../models/mock-users';

@Injectable()
export class UserService {
    getUsers() {
         return Promise.resolve(USERS);
    }
}

views/my-component.component.ts

// imports here...

@Component({
   // ...
})

export class MyComponent {
   constructor(private _userService: UserService) { }

   getUsers() {
         this._userService.getUsers().then(users => console.log(users));
   }
}
setholopolus
  • 253
  • 3
  • 17
dragonmnl
  • 14,578
  • 33
  • 84
  • 129

3 Answers3

59

In your case, it might be more convenient to use an interface for User instead.

export interface User {
    id: number;
    name?: string; // interfaces allow fields to be optional
}

I use interfaces when I want to define the 'shape' of an object, classes when I need to add behavior (methods). If all you need is to move data around, I'd use an interface.

If you do need a class, then the syntax for creating instances of Users in mock-users.ts should be a bit different. Firstly, there's no "optional class fields" in TypeScript. Any field can be not set/'undefined', so it doesn't make sense mark a field optional. You can instantiate a class with the new keyword - the downside is that you need to write a constructor to set field values or assign the instance to a variable and set fields. But there's nothing wrong with using the new keyword, especially for test objects.

You can also instantiate an object with an object literal as you've done in mock-users.ts - you need to be more explicit by adding a cast.

export var USERS: User[] = [
    <User> { // explicit cast to let the TypeScript compiler know you know what you're doing
       id: 1
       // no name (I wanted it to be optional for user with id 1)
    },
    {
       id: 2,
       name: "User 2"
    },        
]

Without the cast, the TypeScript compiler will produce an error. This is by design to catch a mistakes. If you want to know more about the (interesting) reasons, here's a related detailed discussion on some of the thinking behind the type checking:

Community
  • 1
  • 1
Ronald Zarīts
  • 11,819
  • 8
  • 39
  • 42
  • thanks Ronald.could you provide a full example please(usage)? – dragonmnl Apr 20 '16 at 14:32
  • If all the code you have is in the question, then changing the keyword from `class` to `interface` should do the trick. I'll expand the answer. – Ronald Zarīts Apr 20 '16 at 14:35
  • thanks for the further explanation. I'll do that. Could you please explain what should I do when I want both optional parameters and methods? also, in the case of a class with methods, do I need to instantiate with new keywork? after this I'll accept your answer :) – dragonmnl Apr 20 '16 at 14:40
  • thanks for the further explanations. as promised I accepted the answer. could you please define 'anonymous object'? – dragonmnl Apr 20 '16 at 15:14
  • Sorry, the correct term is 'object literal'. The term 'anonymous object' comes from the C# language, which I also like very much. An object literal is just an object defined inline, for example, { id: 1, 'name: 'John' } – Ronald Zarīts Apr 20 '16 at 15:16
  • According to Angular official style guides it is recommended to use class instead of interface. [Check here](https://angular.io/guide/styleguide#interfaces) – Hristo Enev Sep 18 '17 at 10:30
2
class User {
    constructor(public id: number, public name: string = null) {}
}

var USERS: User[] = [
    new User(1),
    new User(2, 'User 2')
];

console.log(USERS);

JsFiddle

Using a constructor will probably make your life a lot easier. You won't get undefined when you try to get a User's name and you'll be able to define functions on the class. The only difference is that you'd use new User(...) instead of just using an object literal as a type.

Corey Ogburn
  • 24,072
  • 31
  • 113
  • 188
1

One other way is to use Object.assign to extend a valid typed object with the property you only need (omitting property a and c in this example)

export class A {
    a:number=1;
    b:number;
    c:string;
    d:string;
}

let validA:A = Object.assign(new A(),{
    b:3,
    d:'Lorem ipsum'
});

I personnaly prefer this syntax to avoid multiline object initialisation and the boring thing to create an interface (so another file quite useless if a coresponding class exists) for each model of my app.

In addition, don't hesitate to set default value in your class definition, even if it's not mandatory for this case.

One last important thing is that you don't loose class methods in this case (opposing to {} casting)

Nicolas Janel
  • 3,025
  • 1
  • 28
  • 31