Search code examples
javascripttypescriptaccessorindex-signature

typescript index signature accessors


I would like my javascript data to be like this:

"dogs": {
    "ada": {
        age: 7,
        breed: poodle
    },
    "levin": {
        age: 5,
        breed: shitzu
    },
    ...
}

Where the name is an object key. I have this typescript class:

export class Dog {
  [dogName: string]: DogDetails;
}

The only way I could see that I could encapsulate methods to create accesssors is by adding these static methods to Dog:

  public static getDogName(dog: Dog): string {
    return Object.keys(dog).toString();
  }

  public static getDogDetails(dog: Dog): DogDetails {
    return dog[Dog.getDogName(dog)];
  }

Which is pretty horrible. One reason being is that static access is required:

Dog.getDogName(dog)  // this is how it must be done

And seems so contrary to how this should be written, which would be:

dog.getName();  // this unfortunately can't be done

The situation would be much nicer if there was a way to get access to the index signature value. However I could not find any way to achieve this. Is there a way or is there a better way to write this class?

Note that this is not possible with classes using index signatures:

public getName(): string {   // this won't work
    Object.keys(this).toString();
}

Since it complains Property 'getName' of type '() => string' is not assignable to string index type 'DogDetails'


Update after answer from @amiramw and comments conversation:

The angular component where this is being used is:

<ion-list *ngFor="let dog of dogs">
    <ion-item>
      <ion-avatar item-start>
        <img src="assets/img/dog.jpg"/>
      </ion-avatar>
      <h4>{{Dog.getName(dog)}}</h4>
      <p>{{Dog.getDogDetails(dog).breed}}</p>
    </ion-item>
  </ion-list>

After the feedback it seems I'll need to write:

<ion-list *ngFor="let dogName of getDogNames()">
    <ion-item>
      <ion-avatar item-start>
        <img src="assets/img/dog.jpg"/>
      </ion-avatar>
      <h4>{{dogName}}</h4>
      <p>{{getDogDetails(dogName).breed}}</p>
    </ion-item>
  </ion-list>

Where getDogNames() is:

  public getDogNames(): string[] {
    if (this.dogs) {
      return Object.keys(this.dogs);
    } else {
      return new Array<string>();
    }
  }

And getDogDetails() is:

  public getDogDetails(name: string): DogDetails {
    return this.dogs[name];
  }

Which works but isn't what I was looking for. I will give @amiramw credit for the suggestion as to a solution to my overall problem.

However I'd much appreciate knowing if there is a way to obtain the typescript index signature value??


Solution

  • You can create an interface for each dog's data and define a map from string to that interface. The interface can be named (for reuse) or anonymous:

    enum Breed {
        poodle,
        shitzu
    }
    let dogs : { [key:string]: {age: number, breed: Breed} } = {
        "ada": {
            age: 7,
            breed: Breed.poodle
        },
        "levin": {
            age: 5,
            breed: Breed.shitzu
        }
    };