0

--

Update: apologies, I have updated my question and code, it lacked the indexer - the key element.

I have a TypeScript class:

export class TestClass {
    something: string = "";
    [index: string]: any;
}

The indexer is so I can dynamically read from/assign to existing class members. E.g. test[something] = "Hi!";

However when I import this & use in another TS class, I am able to do:

var test: TestClass = new TestClass();
test.aPropertyNotInTheClass = "Why can I do this???";

Why is this possible? The indexer is to allow dynamic read/write, but I didn't want to lose type safety.

  • Not sure why you are not getting an error, you should get an error http://www.typescriptlang.org/play/#src=class%20TestClass%20%7B%0D%0A%20%20%20%20something%3A%20string%20%3D%20%22%22%3B%0D%0A%7D%0D%0Avar%20test%3A%20TestClass%20%3D%20new%20TestClass()%3B%0D%0Atest.aPropertyNotInTheClass%20%3D%20%22Why%20can%20I%20do%20this%3F%3F%3F%22%3B%20%20%20 – Titian Cernicova-Dragomir Apr 17 '18 at 10:22
  • Yes, I checked that too. Can you post a screen capture of the error? And maybe your `tsconfig.json`. – Oscar Paz Apr 17 '18 at 10:23
  • You might find the discussion in [How do I dynamically assign properties to an object in TypeScript?](https://stackoverflow.com/questions/12710905/how-do-i-dynamically-assign-properties-to-an-object-in-typescript) useful. (And yes I _do_ realise this us not what you're trying to do) – stuartd Apr 17 '18 at 11:08
  • 1
    _"so I can dynamically read from/assign to **existing** class members."_ How the compiler could know at compile time which members exist at runtime? – Paleo Apr 17 '18 at 13:19
  • What are "existing" class members? The ones you explicitly declare (like `something`?) If so, just remove the index signature (why do you think you need it?). If not, please spell out what you mean. – jcalz Apr 17 '18 at 14:59
  • Why "hack" the language like this, instead of using something like a Map member in the class? – funkizer Apr 17 '18 at 17:20

1 Answers1

0

The indexer is so I can dynamically read from/assign to existing class members. E.g. test[something] = "Hi!";

My guess is you ran into this scenario when using noImplicitAny:

class TestClass {
    something: string = "";
}

const test: TestClass = new TestClass();
const key: string = "something";
test[key] // Error: Element implicitly has an 'any' type because type 'TestClass' has no index signature.

And so you added an index signature (as the error mentions) to make the error go away:

class TestClass {
    something: string = "";
    [index: string]: any;
}

However, as you've seen, this changes the type to allow any kind of access, not just known properties. This isn't what you want.

The real problem is that you are using an index lookup of type string, so the compiler can't resolve it to a known property (since a string could be anything). If it was a known key, like "something", you'd get no error:

const test: TestClass = new TestClass();
const key = "something";
test[key] // OK

That works because key is a literal type "something", not a general string that could be anything. So most likely the real fix in your code is to constrain the key to known keys of the type. You can use keyof and map types to help you.

function lookup(test: TestClass, key: keyof TestClass) {
  return test[key]; // OK
}
lookup(test, "something"); // OK
lookup(test, "notInClass"); // Error

Alternative

Another option you have is to suppress implicit any index errors completely with a compiler option: suppressImplicitAnyIndexErrors. This means that when dealing with a type that has no index signature you can still only use . property access with known properties, so test.aPropertyNotInTheClass is not allowed, but you can use index lookups with any key and you will get no compile error, you will just get the type any. However keep in mind that since the index lookup is going to give you type any it could be undefined and you will get runtime errors if you don't check:

// with --suppressImplicitAnyIndexErrors
const test: TestClass = new TestClass();
const key = "aPropertyNotInTheClass";
test[key] // No compile error, but will be undefined
test[key].toUpperCase() // No compile error, but will be runtime error

This is the the type of error that noImplicitAny exists to prevent. The suppressImplicitAnyIndexErrors is loosening the restrictions since this is a known pain point but comes with pitfalls, which is why it is not enabled by default.

Aaron Beall
  • 49,769
  • 26
  • 85
  • 103