Search code examples
angulartypescriptunion-types

typescript Property 'options' does not exist on type of Union type


I have a couple of interface and a union type:

export interface FieldOptions {
  value: string | number;
  viewValue: string;
}

export interface BaseField {
  id: string;
  derived?: boolean;
  required?: boolean;
}

export interface NumberField extends BaseField {
  type: 'number';
  decimals: number;
}
export interface SelectField extends BaseField {
  type: 'select' | 'select-multi';
  options: FieldOptions[];
  source?: string;
}

export type Field = NumberField | SelectField;

As I understand that when I use my union type Field, it mains a field can be either Number or Select.

With that knowledge, I have a function in my Angular Component

getField( key: string ): Field {
  return this.fields.filter( el => el.id === key )[ 0 ];
}

getFieldVersion2( key: string ): any {
  const field = this.fields.filter( el => el.id === key )[ 0 ];
  if ( (<SelectField>field).options ) {
    return field as SelectField;
  } else {
    return field as Field;
  }
}

With only the function getField which is called in my template to get a Field object, in this case a Select with an Options array of view/viewValues, I get a build error on TS:

Property 'options' does not exist on type 'Field'.
   Property 'options' does not exist on type 'NumberField'.

In my understanding Field is a union type of either Number or Select. One of those has an options property, the other one doesn't.

So I created getFieldVersion2 to hopefully solve the issue. However, unless I add any as a result type of my function, it will trigger a build error.

The template where the options are being used which triggers the compile error:

<mat-form-field  class="input">
  <mat-select formControlName="state">
    <mat-option *ngFor="let option of getField( 'state' ).options" [value]="option.value">
       {{option.viewValue}}
    </mat-option>
  </mat-select>
</mat-form-field>

What is going on, and what should I do better?


Solution

  • You created an interface that does not have an "options" property. You then set your "Field" type equal to that interface (or potentially equal to another interface that does have the "options" property).

    You are then attempting to call the "options" property that may or may not exist. This is why you are receiving the ts error when compiling.

    Switching the return type to "any" works because the compiler will not try to guess the type of object you are returning (therefore it is plausible that "options" will be a valid property and therefore no error should be thrown).

    If you are going to call the "options" property on the "Field" type, you should make sure it exists before trying to compile your ts script.

    Perhaps updating your methods to "getSelectField" and "getNumberField" would be a better option....but of course there are multiple ways you could handle this.