mrkernelpanic
mrkernelpanic

Reputation: 4471

Angular Pipe and TypeScript Type Guard

I read about type guards in typescript here and here. But I still get compiler errors.

Error:(21, 14) TS2349: Cannot invoke an expression whose type lacks a call signature. Type '{ (callbackfn: (value: Foo, index: number, array: Foo...' has no compatible call signatures.

I got following classes:

Foo.ts

export class Foo {
  expired: boolean;
}

Bar.ts

export class Bar {
  foo: Foo;
}

MyPipe.ts

import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'myPipe'
})
export class MyPipe implements PipeTransform {

  transform(items: Foo[] | Bar[], isExpired: Boolean): Foo[] | Bar[] {
    if (!items) {
      return items;
    }

    if (items[0] instanceof Foo) {
      return items.filter((foo: Foo) => {
        return foo.expired == isExpired;
      });
    } else {
      return items.filter((bar: Bar) => {
        return bar.foo.expired == isExpired;
      });
    }
  }
}

The question is, how can I achieve the use of union binding for my parameter "items" and the type guard usage at the same time in my angular pipe using typescript?

Upvotes: 1

Views: 1863

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250336

Typescript will generally not narrow the type of a variable based on the type of a field (with the exception of discriminated unions). More specifically typescript will do no narrowing based on array indexing (this is a known limitation)

The simplest thing you can do is use a type assertion, or the more elegant solution, a custom type guard:

class Foo { private x: string; expired: boolean }
class Bar { private x: string; foo: Foo }

function isArrayOf<T>(ctor: new (...args: any[]) => T, arr: any): arr is T[] {
    return arr[0] instanceof ctor
}

export class MyPipe {
    transform(items: Foo[] | Bar[], isExpired: Boolean): Foo[] | Bar[] {
        if (!items) {
            return items;
        }

        if (isArrayOf(Foo, items) {
            return items.filter((foo: Foo) => {
                return foo.expired == isExpired;
            });
        } else {
            return items.filter((bar: Bar) => {
                return bar.foo.expired == isExpired;
            });
        }
    }
}

Playground link

Upvotes: 1

Related Questions