3

I have the following class:

export class SomeModel {
  prop1: number;
  prop2: number;
  comment: string;
}

and the following method to dynamically get its properties:

getTypeProperties<T>(obj: T): string[] {
    const ret: string[] = [];
    for (const key in obj) {
      if (obj.hasOwnProperty(key))
        ret.push(key);
    }
    return ret;
}

The following call returns an empty array:

getTypeProperties(new SomeModel());

However, if I explicitly initialize all properties with null, the properties will be returned correctly:

export class SomeModel {
  prop1: number = null;
  prop2: number = null;
  comment: string = null;
}

Question: Is this normal behavior? Or is there a TypeScript compiler switch to toggle this?

I do not know if it is relevant, but here is the tsconfig.json content:

{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2017",
      "dom"
    ]
  }
}
Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164

1 Answers1

3

This is by design, field declarations do not output any JavaScript code, they just tell the compiler that the field exists (ie. it's expected don't complain when I use it in code) and is of a certain type. Until you first assign the field it will not exist on the instance and thus will not be iterated. If you initialize the field it's value will be assigned to the instance in the constructor and will thus become iterable.

As you have discovered the simplest way around this is to assign a value to the field, if only the value undefined.

We can see this behavior in the code generated for ES5. For example for this class

class A {
    nonInitField: number;
    initField = 0;
    test() {
        this.nonInitField = 0;// Can be used, and will be iterable after it is assigned
    }
}

This code is generated:

var A = /** @class */ (function () {
    function A() {
        this.initField = 0; // Iterable right away as it is assigned in the constructor
    }
    A.prototype.test = function () {
        this.nonInitField = 0; // Can be used, and will be iterable after it is assigned
    };
    return A;
}());
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • Yes, in this case I prefer null initialization since I have lots of "nullable" properties. Coming from the .NET world, TypeScript is rather strange. Thanks. – Alexei - check Codidact Jun 27 '18 at 08:09
  • @Alexei I'll agree it has some quirks :) `null` works as well. – Titian Cernicova-Dragomir Jun 27 '18 at 08:10
  • 'undefined' worked perfectly. i could iterate all the fields as keys but the object only contained values for the fields I set. with nulls I got all the fields with nulls not just the fields I set. – JesseBoyd Jun 18 '20 at 16:50
  • I am not happy with this solution. I would like to avoid having to initialize fields with any values. Also this solution will not work well with strictNullChecks. It would have been much better if TypeScript had a flag to turn this behavior off. – Shachar Har-Shuv Sep 13 '21 at 12:49