最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How to properly infer types and interfaces onto an Angular Component - Stack Overflow

programmeradmin2浏览0评论

I created a custom select component to use in my forms and want it to be flexible enough to handle multiple types while still being type safe. I have a few types in my project which look like this.

export type DairyType = 'cow' | 'goat';

export type DairyForm = 'milk' | 'cream' | 'yogurt' | 'butter' | 'cheese';

export type MilkType = 'low fat' | 'skim' | '2%' | 'whole' | 'butter milk';

export type MilkState = 'powdered' | 'liquid' | 'condensed';

export type CreamType = 'half & half' | 'heavy cream' | 'sour cream';

export type CreamState = 'liquid' |  'whipped' | 'frozen';

export type YogurtType = 'plain' | 'flavored';

export type ButterType = 'unsalted' | 'salted';

export type CheeseTextureType = 'smooth/creamy' | 'smooth/chunky' | 'crumbly' | 'solid';

export type CheeseHydrationType = 'very wet' | 'wet' | 'damp' | 'dry' | 'very dry';

export type CheeseState = 'spreadable' | 'shredded' | 'crumbled' | 'block' | 'ball' | 'sliced';

That's just for the different types of dairy products alone. I want to be able to apply these types to my select component in each instance so it can be both flexible and type safe. So far been able to think to try is this,

export class SelectInputComponent<ValueType> {

  @Input() OptionType! : ValueType;
  @Input() OptionList! : ValueType[];
  @Input() Control!    : FormControl< ValueType | null>;
  @Input() Label!      : string;

  ToggleOptions = false;

  updateValue(value : ValueType) : void {

    this.Control.setValue(value);
    this.ToggleOptions = false;

  }

  toggleOptions(){ this.ToggleOptions = !this.ToggleOptions; }
  
}

Then in the parent component's template I can do this

<lib-select-input
  ngDefaultControl
  [Control]="Control.controls.type"
  [OptionType]="'CreamType'"
  [OptionList]="[ 'half & half', 'heavy cream', 'sour cream']"
  [Label]="'type'"
/>

The theory is if I can pass the type into the OptionType input, it can be inferred onto the ValueType generic defined on the component class which can then be inferred onto the OptionList and Control properties and the argument type of the updateValue() method.

It eventually hit me that whatever type I could pass in wouldn't be magically imported inside the component and upon testing this component it works, however it's as if everything is of type any because I can literally pass anything into the OptionType and OptionList inputs and it will just accept whatever.

I got the idea because I was thinking about it in terms of if I had something like this

export interface SomeInterface {

    propA : string;
    propB : number;
    propC : SomeOtherInterface;
    propD : boolean;

}

If I were to pass an object of type SomeInterface into the OptionType input the shape of that object will get inferred onto ValueType because there's an actual chunk of data that can be read and understood by the compiler to discern how ValueType should be shaped but when it comes to something like 'milk' | 'cream' | 'yogurt' | 'butter' | 'cheese'.... I'm not sure about how I could infer that in this manner because doing it as shown in my example of the parent component's template, type string just gets inferred which is what allows me to enter anything and it will allow it. Does anybody know how I could get this to work?

I created a custom select component to use in my forms and want it to be flexible enough to handle multiple types while still being type safe. I have a few types in my project which look like this.

export type DairyType = 'cow' | 'goat';

export type DairyForm = 'milk' | 'cream' | 'yogurt' | 'butter' | 'cheese';

export type MilkType = 'low fat' | 'skim' | '2%' | 'whole' | 'butter milk';

export type MilkState = 'powdered' | 'liquid' | 'condensed';

export type CreamType = 'half & half' | 'heavy cream' | 'sour cream';

export type CreamState = 'liquid' |  'whipped' | 'frozen';

export type YogurtType = 'plain' | 'flavored';

export type ButterType = 'unsalted' | 'salted';

export type CheeseTextureType = 'smooth/creamy' | 'smooth/chunky' | 'crumbly' | 'solid';

export type CheeseHydrationType = 'very wet' | 'wet' | 'damp' | 'dry' | 'very dry';

export type CheeseState = 'spreadable' | 'shredded' | 'crumbled' | 'block' | 'ball' | 'sliced';

That's just for the different types of dairy products alone. I want to be able to apply these types to my select component in each instance so it can be both flexible and type safe. So far been able to think to try is this,

export class SelectInputComponent<ValueType> {

  @Input() OptionType! : ValueType;
  @Input() OptionList! : ValueType[];
  @Input() Control!    : FormControl< ValueType | null>;
  @Input() Label!      : string;

  ToggleOptions = false;

  updateValue(value : ValueType) : void {

    this.Control.setValue(value);
    this.ToggleOptions = false;

  }

  toggleOptions(){ this.ToggleOptions = !this.ToggleOptions; }
  
}

Then in the parent component's template I can do this

<lib-select-input
  ngDefaultControl
  [Control]="Control.controls.type"
  [OptionType]="'CreamType'"
  [OptionList]="[ 'half & half', 'heavy cream', 'sour cream']"
  [Label]="'type'"
/>

The theory is if I can pass the type into the OptionType input, it can be inferred onto the ValueType generic defined on the component class which can then be inferred onto the OptionList and Control properties and the argument type of the updateValue() method.

It eventually hit me that whatever type I could pass in wouldn't be magically imported inside the component and upon testing this component it works, however it's as if everything is of type any because I can literally pass anything into the OptionType and OptionList inputs and it will just accept whatever.

I got the idea because I was thinking about it in terms of if I had something like this

export interface SomeInterface {

    propA : string;
    propB : number;
    propC : SomeOtherInterface;
    propD : boolean;

}

If I were to pass an object of type SomeInterface into the OptionType input the shape of that object will get inferred onto ValueType because there's an actual chunk of data that can be read and understood by the compiler to discern how ValueType should be shaped but when it comes to something like 'milk' | 'cream' | 'yogurt' | 'butter' | 'cheese'.... I'm not sure about how I could infer that in this manner because doing it as shown in my example of the parent component's template, type string just gets inferred which is what allows me to enter anything and it will allow it. Does anybody know how I could get this to work?

Share Improve this question edited Mar 31 at 3:55 Optiq asked Mar 31 at 3:48 OptiqOptiq 3,3165 gold badges41 silver badges80 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

Ok the answer JUST came to me which I can't believe I didn't think about it sooner. All I have to do is in my parent component's class define a property as follows

export class ParentComponent{

    // stuff

    SelectType : CreamType = 'half & half';

    // more stuff

}

Then in the template I can do this

<lib-select-input
  ngDefaultControl
  [Control]="Control.controls.type"
  [OptionType]="SelectType"
  [OptionList]="[ 'half & half', 'heavy cream']"
  [Label]="'type'"
/>

Once I pass SelectType into the OptionType input, the items in OptionList will throw errors / warnings if they're not of the same type as the SelectType property.

UPDATE

Ok I got excited too fast. Even though this works, it seems the compiler still looks at 'thing01' | 'thing02' | 'thing03' and rationalizes it as "a variety of strings" which still winds up allowing me to pass any array of strings into the OptionList input.

发布评论

评论列表(0)

  1. 暂无评论