1

I have question about objects. I would like save values from form to precise class object.

For example. I have class TAX

export class TAX
{
    public name: string;
    public description?: string;
    public value: number;
    public enabled: boolean = true;
}

The data that I received from form:

{name: "2000", value: "5", enabled: true, description: "adfdasfdasfasd"}

Now I would like to create new variable of type TAX with data from "simple" object. Is there any build-in function/method or something like that?

I have this solution only:

let entity: TAX = Object.assign(new TAX(), this.form.value);

Is it the best way?

Tom Faltesek
  • 2,768
  • 1
  • 19
  • 30
Micchaleq
  • 433
  • 1
  • 5
  • 21
  • Your method "works", provided there's no funny business going on with the class, but isn't type-safe. – Ingo Bürk Nov 25 '19 at 21:01
  • Possibly related: https://stackoverflow.com/questions/22885995/how-do-i-initialize-a-typescript-object-with-a-json-object/ – Ingo Bürk Nov 25 '19 at 21:02

2 Answers2

1

You can use type assertions to convince the compiler to treat your form as an instance of that class.

const entity = this.form.value as TAX;

Sometimes you’ll end up in a situation where you’ll know more about a value than TypeScript does. Usually this will happen when you know the type of some entity could be more specific than its current type.

Type assertions are a way to tell the compiler “trust me, I know what I’m doing.” A type assertion is like a type cast in other languages, but performs no special checking or restructuring of data. It has no runtime impact, and is used purely by the compiler. TypeScript assumes that you, the programmer, have performed any special checks that you need.

I do this frequently in my Angular projects. Typically I'll have a service method that may accept a certain type (usually a model) as an input. I structure my form to match the shape of the input type and then use type assertion on the form value.

Make sure you validate your form before doing this.

Community
  • 1
  • 1
Tom Faltesek
  • 2,768
  • 1
  • 19
  • 30
  • 1
    In this case you wouldn't know better than the compiler, you'd straight out lie to it and should yourself in the foot in the process. `instanceof` would fail to work, yiu can't use any class methods, ... – Ingo Bürk Nov 25 '19 at 20:59
  • Yes -- Great points about `instanceof` and class methods. It's not *really* an instance of that class. These may be harmless concerns if you're simply doing this with an anemic model class to shuttle some fields around. – Tom Faltesek Nov 25 '19 at 21:11
  • In that case you can just use an interface, though. – Ingo Bürk Nov 26 '19 at 05:14
  • Yeah, I no argument here. Interface would be best. – Tom Faltesek Nov 26 '19 at 15:20
1

If you're declaring a class without the intent to use instanceof or member methods, what you most likely want is just an interface, which would also happen to simplify your logic. The only downside with an interface is that you can't use field initializers like enabled: boolean = true;

export interface Tax {
    name: string;
    description?: string;
    value: number;
    enabled?: boolean;
}

...

const entity: Tax = this.form.value/* as Tax */;

In TypeScript, this is the more conventional solution to your problem, since the only purpose of your awkward Object.assign(new Tax(), ...) was to satisfy the type-check of your class declaration when you really just needed an interface instead.


If in fact you do need to use instanceof or member methods, I would instead suggest adding an analogous interface, and create a constructor for your class:

export interface ITax {
    name: string;
    description?: string;
    value: number;
    enabled?: boolean;
}

export class Tax implements ITax {
    name: string;
    description: string | undefined;
    value: number;
    enabled: boolean;

    constructor ({ name, description, value, enabled = true }: ITax) {
        this.name = name;
        this.description = description;
        this.value = value;
        this.enabled = enabled;
    }
};

...

const entity: Tax = new Tax(this.form.value/* as ITax */);

This way, you're not lying about the type of this.form.value or entity.

Some would argue that it's bad practice to have interfaces with I prefixes in TypeScript, but in this particular case I would argue it makes sense to have because your this.form.value is a plain object that implements the interface, while Tax denotes a proper instance of the respective class, which allows language constructs like instanceof checks, access to member methods, etc., that are not part of the base interface ITax.

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153