Property in type is not assignable to the same property in base type
Asked Answered
C

2

18

Hi I've a basic example where I keep getting this error and I am not sure what exactly the problem is.

Basically I want to pass different types to class A at runtime. In the given example it's Order but I will write other classes for Product, Customers and more and for all of them I want to use A<T> as parent. I've also added the real code below the minimal example.

Can please someone explain the solution and the problem to this.

interface Order{
    customer: {
        name: string;
    };
}

abstract class A<T>{
    protected abstract matches<T>(tableData: T, term: string): boolean;
}

class B extends A<Order>{

    protected matches<T extends Order>(tableData: T, term: string): boolean {
        return tableData.customer.name.toLowerCase().includes(term.toLowerCase());
    }
}

The goal is to access tableData.customer within class B. I want to pass Order to the matches method as a Generic type but I am not sure how this can be implemented the right way.

Update

Parent Class:

export abstract class TableFilterService<T> {
  private _loading$ = new BehaviorSubject<boolean>(true);
  private _search$ = new Subject<void>();
  private _total$ = new BehaviorSubject<number>(0);
  private _tableData$: BehaviorSubject<T[]> = new BehaviorSubject([]);
  private _data$!: T[];

  protected _state: State = {
    page: 1,
    pageSize: 50,
    searchTerm: '',
    sortColumn: '',
    sortDirection: ''
  };

  private compare = (v1: string | number, v2: string | number) => v1 < v2 ? -1 : v1 > v2 ? 1 : 0;
  protected abstract matches<T>(tableData: T, term: string, pipe: PipeTransform): boolean;

  get tableData$() { return this._tableData$.asObservable(); }
  get search$() { return this._search$.asObservable(); }
  get total$() { return this._total$.asObservable(); }
  get loading$() { return this._loading$.asObservable(); }
  get page() { return this._state.page; }
  get pageSize() { return this._state.pageSize; }
  get searchTerm() { return this._state.searchTerm; }
  get data() { return this._data$; }

  set data(data: T[]) { this._data$ = data; }
  set page(page: number) { this._set({page}); }
  set pageSize(pageSize: number) { this._set({pageSize}); }
  set searchTerm(searchTerm: string) { this._set({searchTerm}); }
  set sortColumn(sortColumn: SortColumn) { this._set({sortColumn}); }
  set sortDirection(sortDirection: SortDirection) { this._set({sortDirection}); }

  constructor(protected pipe: DecimalPipe) {

    this._search$.pipe(
      tap(() => this._loading$.next(true)),
      debounceTime(200),
      switchMap(() => this._search()),
      delay(200),
      tap(() => this._loading$.next(false))
    ).subscribe(result => {
      this._tableData$.next(result.tableData);
      this._total$.next(result.total);
    });

    this._search$.next();
  }

  protected sort<T>(orders: T[], column: SortColumn, direction: string): T[] {
    if (direction === '' || column === '') {
      return orders;
    } else {
      return [...orders].sort((a: any, b: any) => {
        const res = this.compare(a[column], b[column]);
        return direction === 'asc' ? res : -res;
      });
    }
  }

  protected _search(): Observable<SearchResult<T>> {
    const {sortColumn, sortDirection, pageSize, page, searchTerm} = this._state;

    // 1. sort
    let tableData = this.sort(this.data, sortColumn, sortDirection);

    // 2. filter
    tableData = tableData.filter(data => this.matches(data, searchTerm, this.pipe));
    const total = tableData.length;

    // 3. paginate
    tableData = tableData.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);

    return of({ tableData, total});
  }

  private _set(patch: Partial<State>) {
    Object.assign(this._state, patch);
    this._search$.next();
  }
}

Child Class:

export class OrderTableService extends TableFilterService<Order>{

  protected matches(tableData: Order, term: string, pipe: PipeTransform): boolean {
    return tableData.customer.name.toLowerCase().includes(term.toLowerCase());
  }
}

Error:

enter image description here

The complete error:

Property 'matches' in type 'OrderTableService' is not assignable to the same property in base type 'TableFilterService<Order>'. Type '(tableData: Order, term: string, pipe: PipeTransform) => boolean' is not assignable to type '<T>(tableData: T, term: string, pipe: PipeTransform) => boolean'. Types of parameters 'tableData' and 'tableData' are incompatible. Type 'T' is not assignable to type 'Order'.ts

Cretin answered 25/3, 2021 at 16:50 Comment(3)
Why does your matches method need a type parameter T extends Order anyway, when this is only used for a parameter? The method would be usable in exactly the same circumstances if its parameter type were simply Order, since any argument of a type T which is a subtype of Order would be assignable to a parameter of type Order anyway.Succor
Order is only one type, I would have to pass more types also. I'll have more classes like ProductTableService where the type passed to matches would be ProductCretin
If ProductTableService is a subtype of Order then you can already pass it without needing a generic type. If it's not a subtype of Order, then you can't pass it because it doesn't satisfy the type parameter's upper bound.Succor
C
5

Your abstract function generic matches<T> overrides generic T of the class A<T>. What you wrote is basically this:

abstract class A<T>{
    protected abstract matches<U>(tableData: U, term: string): boolean;
}

The generic T at the top of the class was basically useless. The solution could look like this:

interface Order{
    customer: {
        name: string;
    };
}

abstract class A<T>{
    // No generic <T> at the function itself anymore. Now tableData: T refers to A<T>
    protected abstract matches(tableData: T, term: string): boolean;
}

class B extends A<Order>{

    protected matches(tableData: Order, term: string): boolean {
        return tableData.customer.name.toLowerCase().includes(term.toLowerCase());
    }
}

class C extends A<Order>{

    protected matches<T extends Order>(tableData: T, term: string): boolean {
        return tableData.customer.name.toLowerCase().includes(term.toLowerCase());
    }
}
Circumspect answered 25/3, 2021 at 17:11 Comment(7)
Hi thanks for the answer. The problem is that class A has a lot of general stuff that will not only be used by Order but will also be used by other interfaces for example Product. If I write T extends order in class A definition than that could make my code less general.Cretin
That was a copy mistake of mine already removed it in an update.Circumspect
It's weird I tried your example but the compiler still complains, check update. You know what could be the reason, i tried your example before also and i thought i am doing something wrong maybe. The compiler keeps reporting an errorCretin
How does TableFilterService definition looks like. If you check the playground i linked you can see that it works for your simple definition. Need more info to help you there.Circumspect
i've posted both the parent and child classes in the updateCretin
As i wrote in the first part of my answer, the <T> of matches is shadowing the <T> at the class. Replace protected abstract matches<T>(tableData: T, term: string, pipe: PipeTransform): boolean; with protected abstract matches(tableData: T, term: string, pipe: PipeTransform): boolean;. I've also update the answer with a comment in the code to make it more clear.Circumspect
I thought you were talking about the subclass. I got what you meant now. The base class <T> makes assigns it as a new data type instead of using the one passed to the base class as param by the subclass. Thanks for the explanation.Cretin
S
0

I had the same issue, when using an angular library that was in the same solution as the consuming angular app.

The problem was that somehow I had a node_modules folder inside the angular library's folder. (generated by mistakenly running npm i in the library's folder)

So deleting the node_modules folder in the library project solved the issue.

Stuccowork answered 15/11, 2023 at 9:28 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.