ZiiMakc
ZiiMakc

Reputation: 36826

Class property type guard based on instance property value

How to make class example to infer config type based on animalType instance value check:

enum Animal {
  BIRD = 'bird',
  DOG = 'dog',
}

type Base = {
    id: number
}

// Object example
type Smth = Base &
  (
    | {
        animalType: Animal.BIRD;
        config: number;
      }
    | {
        animalType: Animal.DOG;
        config: string;
      }
  );

// type guards working
const smthObj: Smth = {
  id: 1,
  animalType: Animal.BIRD,
  config: 1
};

// should be error
const smthObj2: Smth = {
  id: 1,
  animalType: Animal.BIRD,
  config: 'x'
};

if (smthObj.animalType === Animal.BIRD) {
 smthObj.config = 1;
 smthObj.config = 'x'; // should be error
}

// How to make it work the same for class?

class myClass {
  id: number;
  animalType: Animal;
  // this should be based on Animal type
  // number for bird and string for dog
  config: number | string; 

   constructor(id: number, animalType: Animal, config: number | string) {
    this.id = id;
    this.animalType = animalType;
    this.config = config
  }
}

const smthClass: myClass = 1 as any

// I need to make only this check to work
if (smthClass.animalType === Animal.BIRD) {
 smthClass.config = 1;
 smthClass.config = 'x'; // should be error
}

Typescript sandbox.

Upvotes: 2

Views: 841

Answers (2)

yqlim
yqlim

Reputation: 7080

Use Generics.

enum Animal {
  BIRD = 'bird',
  DOG = 'dog',
}

class myClass<T extends Animal = Animal> {
  id: number;
  animalType: T;
  config: T extends Animal.BIRD ? number : T extends Animal.DOG ? string : never; // this should be based on Animal type

  constructor(id: number, animalType: T, config: T extends Animal.BIRD ? number : T extends Animal.DOG ? string : never) {
    this.id = id;
    this.animalType = animalType;
    this.config = config
  }
}

const birdClass = new myClass(1, Animal.BIRD, 1);

if (birdClass.animalType === Animal.BIRD) {
  birdClass.config = 1;
  birdClass.config = 'x'; // should be error
}

const dogClass = new myClass(1, Animal.DOG, 'x');

if (dogClass.animalType === Animal.DOG) {
  dogClass.config = 1; // should be error
  dogClass.config = 'x';
}

const smthClass: myClass<Animal.BIRD> = 1 as any;

if (smthClass.animalType === Animal.BIRD) {
  smthClass.config = 1;
  smthClass.config = 'x'; // should be error
}

// Type guard function example for any class
function isBirdClass(smth: myClass): smth is myClass<Animal.BIRD> {
  return smth.animalType === Animal.BIRD ? true : false
}

function isDogClass(smth: myClass): smth is myClass<Animal.DOG> {
  return smth.animalType === Animal.DOG ? true : false
}

const unknownClass: myClass = 1 as any;

if (isBirdClass(unknownClass)) {
  unknownClass.config = 1;
  unknownClass.config = 'x'; // should be error
}

if (isDogClass(unknownClass)){
  unknownClass.config = 1; // should be error 
  unknownClass.config = 'x'; 
}

See it in TypeScript Playground.

Note:

  • Using generics, you don't even need a type guard because the generics will automatically catch type errors.

Upvotes: 1

Class arguments should be a part of one data structure.

This approach is safe and easy

enum Animal {
  BIRD = 'bird',
  DOG = 'dog',
}

type Base = {
  id: number
}

// Object example
type Smth = Base &
  (
    | {
      animalType: Animal.BIRD;
      config: number;
    }
    | {
      animalType: Animal.DOG;
      config: string;
    }
  );

// How to make it work the same for class?

class myClass {
  constructor(public props: Smth) {
    this.props = props;
  }
}

const smthClass: myClass = 1 as any

if (smthClass.props.animalType === Animal.BIRD) {
  smthClass.props.config = 1;
  smthClass.props.config = 'x'; // should be error
}

UPDATE

enum Animal {
  BIRD = 'bird',
  DOG = 'dog',
}


// How to make it work the same for class?

type Either<R> = R extends Animal.DOG ? string : number;

class myClass<T extends Animal> {
  id: number;
  animalType: T;
  config: Either<T>

  constructor(id: number, animalType: T, config: Either<T>) {
    this.id = id;
    this.animalType = animalType;
    this.config = config
  }
}

const smthClass: myClass<Animal.BIRD> = 1 as any

if (smthClass.animalType === Animal.BIRD) {
  smthClass.config = 1;
  smthClass.config = 'x'; // should be error
}

Upvotes: 3

Related Questions