How to type a function that can take and return arrays of strings or arrays of objects?

0

Issue

I have a directive that in Input can get array of string or array of objects. The directive is to filter the list from @Input() and emit a new list in @Output(). Inside the directive, I created a method that does all the magic, but I can’t correctly type @Input() and the function itself.

export class SelectFilteringDirective<T> {
  @Input() readonly optionsList: T[] = [];
  @Output() filteredList = new EventEmitter<T[]>();

  constructor() {}

  @HostListener('keyup', ['$event'])
  onKeyup(event: Event): void {
    this.filteredList.emit(
      this.filterList(
        this.optionsList,
        (event.target as HTMLInputElement).value
      )
    );
  }

  private filterList(data: T[], value: string): any {
    return typeof data[0] === 'object'
      ? [...data].filter(
          (item) => item.value.toLowerCase().indexOf(value.toLowerCase()) != -1
        )
      : [...data].filter(
          (item) => item.toLowerCase().indexOf(value.toLowerCase()) != -1
        );
  }
}

In the variant with the object I get the error:

Property 'value' does not exist on type 'T'.

However, in the variant with array of strings, I get the error:

Property 'toLowerCase' does not exist on type 'T'.

Solution

If I got your question correctly, you have a directive that receives one @Input which can be either an Array of strings or an Array of objects. Based on that, here’s a solution for this:

type KeyValue = Readonly<{
  key: string;
  value: string;
}>;
type ListItem = string | KeyValue;

@Directive({ selector: 'selectFiltering' })
export class SelectFilteringDirective {
  @Input() optionsList: readonly ListItem[] = [];
  @Output() readonly filteredList = new EventEmitter<ListItem[]>();

  @HostListener('keyup', ['$event'])
  onKeyup(event: Event): void {
    this.filteredList.emit(
      this.filterList(
        this.optionsList,
        (event.target as HTMLInputElement).value,
      )
    );
  }

  private filterList(
    list: readonly ListItem[],
    value: string
  ): ListItem[] {
    const lowerCasedValue = value.toLowerCase();
    return list.filter((item) =>
      (typeof item === 'string' ? item : item.value)
        .toLowerCase()
        .includes(lowerCasedValue)
    );
  }
}

Tip:

While this might solve the problem, I’m not too sure @Directive would be the most correct way to do this. Again, if I got your question correctly, you have some options and you want to filter them out based on input changes… if that’s the case, I’d suggest to use a @Pipe (pure, ofc) instead.

Just in case you want to see the @Pipe version, here you go:

DEMO using @Pipe

Answered By – developer033

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave A Reply

Your email address will not be published.

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More