alireza.salemian
alireza.salemian

Reputation: 576

TypeScript type alias for key-value pair

I have an interface as:

export interface PostEventTypeMap {
  changePrice: number;
  changeQuantity: number;
  signalConnected: boolean;
  signalDisconnected: boolean;
}

how I can define a type alias like:

type PostEvent = {
  type: keyof PostEventTypeMap,
  value: **Value based on key value and PostEventTypeMap**
};

I can not make postEvent property generic:

PostBoxService {
  get postEvent(): Observable<PostEvent> {
    return getOrCreateValue(this, "postEvent", () => new BehaviorSubject(null));
  }
}

The usecase of service:

postBoxService
  .postEvent
  .subscribe(event => {
    if (!event)
      return;

    switch (event.type) {
      case "changePrice":
        this.changePrice(event.value);
        break;
      case "changeQuantity":
        this.changeQuantity(event.value);
        break;
    }
  });

Upvotes: 1

Views: 1862

Answers (2)

jcalz
jcalz

Reputation: 328142

Because you have a finite number of possibilities, and since the type value will always be a literal type, you can make this a discriminated union. One way to do this is to map the PostEventTypeMap properties to the union members you want, and then immediately look up these properties to get a union:

type ObjToTypeValueUnion<T extends object> = 
  { [K in keyof T]-?: { type: K, value: T[K] } }[keyof T]

type PostEvent = ObjToTypeValueUnion<PostEventTypeMap>;
/* type PostEvent = {
    type: "changePrice";
    value: number;
} | {
    type: "changeQuantity";
    value: number;
} | {
    type: "signalConnected";
    value: boolean;
} | {
    type: "signalDisconnected";
    value: boolean;
} */

You can verify that this works as desired for your use case:

declare function changePrice(x: number): void;
declare function changeQuantity(x: number): void;
((event?: PostEvent) => {
  if (!event)
    return;

  switch (event.type) {
    case "changePrice":
      changePrice(event.value);
      break;
    case "changeQuantity":
      changeQuantity(event.value);
      break;
  }
});

Playground link to code

Upvotes: 3

Apokralipsa
Apokralipsa

Reputation: 2724

You may need to make the PostEvent type generic, as in:

export interface PostEventTypeMap {
  changePrice: number;
  changeQuantity: number;
  signalConnected: boolean;
  signalDisconnected: boolean;
}

type PostEvent<T extends keyof PostEventTypeMap> = {
  type: T,
  value: PostEventTypeMap[T]
};

const validChangePrice: PostEvent<'changePrice'> = {
  type: 'changePrice',
  value: 1
}

const invalidChangePrice: PostEvent<'changePrice'> = {
  type: 'changePrice',
  value: true // Compile error TS2322: Type 'true' is not assignable to type 'number'.
}

Upvotes: 1

Related Questions