Reputation: 10146
How to init a new class in TS in such a way (example in C# to show what I want):
// ... some code before
return new MyClass { Field1 = "ASD", Field2 = "QWE" };
// ... some code after
Upvotes: 406
Views: 426768
Reputation: 85
The best answer is the rev7 of OP. Here is that revision .=======================================================
When I was writing this question I was pure .NET developer without much of JS knowledge. Also TypeScript was something completely new, announced as new C#-based superset of JavaScript. Today I see how stupid this question was.
Anyway if anyone still is looking for an answer, please, look at the possible solutions below.
First thing to note is in TS we shouldn't create empty classes for models. Better way is to create interface or type (depending on needs). Good article from Todd Motto: https://ultimatecourses.com/blog/classes-vs-interfaces-in-typescript
SOLUTION 1:
type MyType = { prop1: string, prop2: string };
return <MyType> { prop1: '', prop2: '' };
SOLUTION 2:
type MyType = { prop1: string, prop2: string };
return { prop1: '', prop2: '' } as MyType;
SOLUTION 3 (when you really need a class):
class MyClass {
constructor(public data: { prop1: string, prop2: string }) {}
}
// ...
return new MyClass({ prop1: '', prop2: '' });
or
class MyClass {
constructor(public prop1: string, public prop2: string) {}
}
// ...
return new MyClass('', '');
Of course in both cases you may not need casting types manually because they will be resolved from function/method return type.
Upvotes: 0
Reputation: 609
type ExcludeMethods<T> = Pick<T, { [K in keyof T]: T[K] extends Function ? never : K }[keyof T]>;
class Person{
name: string = "N/A";
age: number = 0;
gender?: "male" | "female"
constructor(init?:ExcludeMethods<Person>){
Object.assign(this, init);
}
Describe(){return `${this.name} ${this.age} ${this.gender ?? ""}` }
}
var p1 = new Person();
var p2 = new Person({
name: "John",
age: 20
});
var p3 = new Person({
name: "Mary",
age: 25,
gender: "female"
});
console.log(p1.Describe()) // N/A 0
console.log(p2.Describe()) // John 20
console.log(p3.Describe()) // Mary 25 female
Upvotes: 0
Reputation: 21
how's this...
function as_<T>(o: T) { return o; };
// ... some code before
return as_<MyClass>({ Field1 = "ASD", Field2 = "QWE" });
// ... some code after
Upvotes: 0
Reputation: 3314
type ExcludeMethods<T> = Pick<T, { [K in keyof T]: T[K] extends Function ? never : K }[keyof T]>;
class MyClass {
public name!: string;
public age!: number;
public optional?: boolean;
private yep: string = "";
constructor(props: ExcludeMethods<typeof MyClass.prototype>) {
Object.assign(this, props);
}
public method() {
}
}
const thing = new MyClass({
name: "bob",
age: 15
});
Upvotes: 0
Reputation: 39888
Since writing this answer, better ways have come up. Please see the other answers below that have more votes and a better answer. I cannot remove this answer since it's marked as accepted.
There is an issue on the TypeScript codeplex that describes this: Support for object initializers.
As stated, you can already do this by using interfaces in TypeScript instead of classes:
interface Name {
givenName: string;
surname: string;
}
class Person {
name: Name;
age: number;
}
var bob: Person = {
name: {
givenName: "Bob",
surname: "Smith",
},
age: 35,
};
Upvotes: 119
Reputation: 31
Here is the best solution I've found for this.
Declare a function that can be used as a decorator. I'm calling it AutoReflect
export function AutoReflect<T extends { new(...args: any[]): {} }>(
constructor: T
) {
return class extends constructor {
constructor(...args: any[]) {
super(args)
if (typeof args[0] === 'object') {
Object.assign(this, args[0]);
}
}
};
}
What this does is expects an object in the constructor and assigns the members to the the class instance. Use this on a class declaration
interface IPerson {
name: string;
age: number;
}
@AutoReflect
class Person implements IPerson {
name: string;
number: number;
constructor(model?: Partial<IPerson>){}
}
In the constructor of your model, you can make the model optional, and in using the Partial you can new up an instance without setting all the property values
new Person({
name: 'Santa'
});
This method creates a new instance of the class you want and also has a C# object initialization feeling to it.
Upvotes: 3
Reputation: 1542
For more modern versions of TypeScript
Class definition
export class PaymentRequestDto {
public PaymentSource: number;
public PaymentCenterUid: string;
public ConnectedUserUid: string;
}
And you have some values from somewhere:
const PaymentCenter= 'EA0AC01E-D34E-493B-92FF-EB2D66512345';
const PaymentSource= 4;
const ConnectedUser= '2AB0D13C-2BBE-46F5-990D-533067BE2EB3';
Then you can initialize your object while being strongly typed.
const parameters: PaymentRequestDto = {
PaymentSource,
PaymentCenterUid: PaymentCenter,
ConnectedUserUid: ConnectedUser,
};
PaymentSource doesn't require a name field specifier because the variable used has the same name as the field.
And this works with arrays too.
const parameters: PaymentRequestDto [] = [
{
PaymentSource,
PaymentCenterUid: PaymentCenter,
ConnectedUserUid: ConnectedUser,
},
{
. . . .
}
];
Upvotes: -2
Reputation: 424
Here's a solution that:
Partial<...>
)OnlyData<...>
solution)The only drawback is that it looks more complicated at first.
// Define all fields here
interface PersonParams {
id: string
name?: string
coolCallback: () => string
}
// extend the params interface with an interface that has
// the same class name as the target class
// (if you omit the Params interface, you will have to redeclare
// all variables in the Person class)
interface Person extends PersonParams { }
// merge the Person interface with Person class (no need to repeat params)
// person will have all fields of PersonParams
// (yes, this is valid TS)
class Person {
constructor(params: PersonParams) {
// could also do Object.assign(this, params);
this.id = params.id;
this.name = params.name;
// intellisence will expect params
// to have `coolCallback` but not `sayHello`
this.coolCallback = params.coolCallback;
}
// compatible with functions
sayHello() {
console.log(`Hi ${this.name}!`);
}
}
// you can only export on another line (not `export default class...`)
export default Person;
Upvotes: 2
Reputation: 5974
I suggest an approach that does not require Typescript 2.1:
class Person {
public name: string;
public address?: string;
public age: number;
public constructor(init:Person) {
Object.assign(this, init);
}
public someFunc() {
// todo
}
}
let person = new Person(<Person>{ age:20, name:"John" });
person.someFunc();
key points:
Partial<T>
not requiredUpvotes: 23
Reputation: 69
To init a class without redeclaring all the properties for defaults:
class MyClass{
prop1!: string //required to be passed in
prop2!: string //required to be passed in
prop3 = 'some default'
prop4 = 123
constructor(opts:{prop1:string, prop2:string} & Partial<MyClass>){
Object.assign(this,opts)
}
}
This combines some of the already excellent answers
Upvotes: 5
Reputation: 3245
This is another solution:
return {
Field1 : "ASD",
Field2 : "QWE"
} as myClass;
Upvotes: 3
Reputation: 458
if you want to create new instance without set initial value when instance
1- you have to use class not interface
2- you have to set initial value when create class
export class IStudentDTO {
Id: number = 0;
Name: string = '';
student: IStudentDTO = new IStudentDTO();
Upvotes: -2
Reputation: 427
You could have a class with optional fields (marked with ?) and a constructor that receives an instance of the same class.
class Person {
name: string; // required
address?: string; // optional
age?: number; // optional
constructor(person: Person) {
Object.assign(this, person);
}
}
let persons = [
new Person({ name: "John" }),
new Person({ address: "Earth" }),
new Person({ age: 20, address: "Earth", name: "John" }),
];
In this case, you will not be able to omit the required fields. This gives you fine-grained control over the object construction.
You could use the constructor with the Partial type as noted in other answers:
public constructor(init?:Partial<Person>) {
Object.assign(this, init);
}
The problem is that all fields become optional and it is not desirable in most cases.
Upvotes: 12
Reputation: 12855
I wanted a solution that would have the following:
Here is the way that I do it:
export class Person {
id!: number;
firstName!: string;
lastName!: string;
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
constructor(data: OnlyData<Person>) {
Object.assign(this, data);
}
}
const person = new Person({ id: 5, firstName: "John", lastName: "Doe" });
person.getFullName();
All the properties in the constructor are mandatory and may not be omitted without a compiler error.
It is dependant on the OnlyData
that filters out getFullName()
out of the required properties and it is defined like so:
// based on : https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c
type FilterFlags<Base, Condition> = { [Key in keyof Base]: Base[Key] extends Condition ? never : Key };
type AllowedNames<Base, Condition> = FilterFlags<Base, Condition>[keyof Base];
type SubType<Base, Condition> = Pick<Base, AllowedNames<Base, Condition>>;
type OnlyData<T> = SubType<T, (_: any) => any>;
Current limitations of this way:
Upvotes: 15
Reputation: 3533
You can affect an anonymous object casted in your class type. Bonus: In visual studio, you benefit of intellisense this way :)
var anInstance: AClass = <AClass> {
Property1: "Value",
Property2: "Value",
PropertyBoolean: true,
PropertyNumber: 1
};
Edit:
WARNING If the class has methods, the instance of your class will not get them. If AClass has a constructor, it will not be executed. If you use instanceof AClass, you will get false.
In conclusion, you should used interface and not class. The most common use is for the domain model declared as Plain Old Objects. Indeed, for domain model you should better use interface instead of class. Interfaces are use at compilation time for type checking and unlike classes, interfaces are completely removed during compilation.
interface IModel {
Property1: string;
Property2: string;
PropertyBoolean: boolean;
PropertyNumber: number;
}
var anObject: IModel = {
Property1: "Value",
Property2: "Value",
PropertyBoolean: true,
PropertyNumber: 1
};
Upvotes: 36
Reputation: 1776
If you're using an old version of typescript < 2.1 then you can use similar to the following which is basically casting of any to typed object:
const typedProduct = <Product>{
code: <string>product.sku
};
NOTE: Using this method is only good for data models as it will remove all the methods in the object. It's basically casting any object to a typed object
Upvotes: 1
Reputation: 2970
Below is a solution that combines a shorter application of Object.assign
to more closely model the original C#
pattern.
But first, lets review the techniques offered so far, which include:
Object.assign
Partial<T>
trick within the copy constructorObject.create
instead of Object.assign
Of course, each have their pros/cons. Modifying a target class to create a copy constructor may not always be an option. And "casting" loses any functions associated with the target type. Object.create
seems less appealing since it requires a rather verbose property descriptor map.
So, here's yet another approach that is somewhat simpler, maintains the type definition and associated function prototypes, and more closely models the intended C#
pattern:
const john = Object.assign( new Person(), {
name: "John",
age: 29,
address: "Earth"
});
That's it. The only addition over the C#
pattern is Object.assign
along with 2 parenthesis and a comma. Check out the working example below to confirm it maintains the type's function prototypes. No constructors required, and no clever tricks.
This example shows how to initialize an object using an approximation of a C#
field initializer:
class Person {
name: string = '';
address: string = '';
age: number = 0;
aboutMe() {
return `Hi, I'm ${this.name}, aged ${this.age} and from ${this.address}`;
}
}
// typescript field initializer (maintains "type" definition)
const john = Object.assign( new Person(), {
name: "John",
age: 29,
address: "Earth"
});
// initialized object maintains aboutMe() function prototype
console.log( john.aboutMe() );
Upvotes: 65
Reputation: 2773
In some scenarios it may be acceptable to use Object.create
. The Mozilla reference includes a polyfill if you need back-compatibility or want to roll your own initializer function.
Applied to your example:
Object.create(Person.prototype, {
'Field1': { value: 'ASD' },
'Field2': { value: 'QWE' }
});
In my case I found this useful in unit tests for two reasons:
__proto__
) and fail the test. For example:var actual = new MyClass();
actual.field1 = "ASD";
expect({ field1: "ASD" }).toEqual(actual); // fails
The output of the unit test failure will not yield a clue about what is mismatched.
Finally, the solution proposed at http://typescript.codeplex.com/workitem/334 does not support inline json-style declaration. For example, the following does not compile:
var o = {
m: MyClass: { Field1:"ASD" }
};
Upvotes: 15
Reputation: 26398
Updated 07/12/2016:
Typescript 2.1 introduces Mapped Types and provides Partial<T>
, which allows you to do this....
class Person {
public name: string = "default"
public address: string = "default"
public age: number = 0;
public constructor(init?:Partial<Person>) {
Object.assign(this, init);
}
}
let persons = [
new Person(),
new Person({}),
new Person({name:"John"}),
new Person({address:"Earth"}),
new Person({age:20, address:"Earth", name:"John"}),
];
Original Answer:
My approach is to define a separate fields
variable that you pass to the constructor. The trick is to redefine all the class fields for this initialiser as optional. When the object is created (with its defaults) you simply assign the initialiser object onto this
;
export class Person {
public name: string = "default"
public address: string = "default"
public age: number = 0;
public constructor(
fields?: {
name?: string,
address?: string,
age?: number
}) {
if (fields) Object.assign(this, fields);
}
}
or do it manually (bit more safe):
if (fields) {
this.name = fields.name || this.name;
this.address = fields.address || this.address;
this.age = fields.age || this.age;
}
usage:
let persons = [
new Person(),
new Person({name:"Joe"}),
new Person({
name:"Joe",
address:"planet Earth"
}),
new Person({
age:5,
address:"planet Earth",
name:"Joe"
}),
new Person(new Person({name:"Joe"})) //shallow clone
];
and console output:
Person { name: 'default', address: 'default', age: 0 }
Person { name: 'Joe', address: 'default', age: 0 }
Person { name: 'Joe', address: 'planet Earth', age: 0 }
Person { name: 'Joe', address: 'planet Earth', age: 5 }
Person { name: 'Joe', address: 'default', age: 0 }
This gives you basic safety and property initialization, but its all optional and can be out-of-order. You get the class's defaults left alone if you don't pass a field.
You can also mix it with required constructor parameters too -- stick fields
on the end.
About as close to C# style as you're going to get I think (actual field-init syntax was rejected). I'd much prefer proper field initialiser, but doesn't look like it will happen yet.
For comparison, If you use the casting approach, your initialiser object must have ALL the fields for the type you are casting to, plus don't get any class specific functions (or derivations) created by the class itself.
Upvotes: 659
Reputation: 1445
The easiest way to do this is with type casting.
return <MyClass>{ Field1: "ASD", Field2: "QWE" };
Upvotes: 1
Reputation: 5769
I'd be more inclined to do it this way, using (optionally) automatic properties and defaults. You haven't suggested that the two fields are part of a data structure, so that's why I chose this way.
You could have the properties on the class and then assign them the usual way. And obviously they may or may not be required, so that's something else too. It's just that this is such nice syntactic sugar.
class MyClass{
constructor(public Field1:string = "", public Field2:string = "")
{
// other constructor stuff
}
}
var myClass = new MyClass("ASD", "QWE");
alert(myClass.Field1); // voila! statement completion on these properties
Upvotes: 14