20

I have an abstract class, in the method of which I am passing an item of a generic type. Next, I need to get the property of this item, how to do it correctly?

export abstract class BaseService<T> {
    ...
    public saveItem(item: T) {
        ...
        if (item.id <=== here I got error ) {
        }
        ...
    }

    export class ClusterItem {
        id: number;
        ...
     }

    export class ClustersService extends BaseService<ClusterItem> {
        ...
    }
Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47
Green176
  • 349
  • 2
  • 3
  • 9
  • 1
    You could use [Type Guards](https://www.typescriptlang.org/docs/handbook/advanced-types.html) to check for a type prior to attempting to accessing certain properties or accessing via bracket notation `item['id']` – Alexander Staroselsky Sep 07 '17 at 16:00
  • 3
    Perhaps `T extends { id: number }`, to constrain the generic type to things that have that property. – jonrsharpe Sep 07 '17 at 16:07

3 Answers3

32

Your code assumes that every type T used with your class may have an id property.

As pointed out by Jon Sharpe in his comment on your question, the correct approach, both in terms of type safety and expressiveness, is to declare this assumption as a type constraint, making it explicit and known to the type checker.

The way to do this is to use the type constraint syntax on your generic type parameter.

For example, you could write

export interface MayHaveId {
  id?: number; // you can use any type, number is just an example
}

export abstract class BaseService<T extends MayHaveId> {
  saveItem(item: T) {
    if (item.id !== undefined) {
      console.log(item.id);
    }
  }
} 

Here we've defined an interface to make the code more readable, but a type literal would work as well.

Note that the id property is declared optional since your logic does not require it to have a value.

If you only intend for the base class to be used with type arguments which do have an id then you should make the property required. This will make the code easier to understand and will catch more errors at compile time by ensuring that it is only used with such types in the first place.

Shahbaaz asked about dynamic properties in a comment.

We can define a type with a dynamic property by using a parametric generic type

type WithProperty<K extends string, V = {}> = {
  [P in K]: V
}

We can consume this type

function withProperty<T, K extends string, V> (x: T, properties: WithProperty<K, V>) {
  return Object.assign(x, properties);
}
Chris Farmer
  • 24,974
  • 34
  • 121
  • 164
Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
0

EDITED

For Typescript you can use:

if("id" in item) {
  ...
 }

Ref: Why does Typescript treat `object.hasOwnProperty("key")` as essentially different from `"key" in object`

For JS you can use item.hasOwnProperty('id') before trying to fetch value.

Ref : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty

Surender Khairwa
  • 601
  • 4
  • 17
0

Just extend the generic type parameter as (the same with methods):

const fn = <T extends { id: T['id']}>(items: T[]) => ...

And example usage:

const ids = <T extends { id: T['id']}>(items: T[]) => items.map((item) => item.id);

const numbers = ids([{ id: 1 }, { id: 2 }, { id: 3 }]);
const strings = ids([{ id: 'a' }, { id: 'b' }, { id: 'c' }]);

// type check error:
const typeError = ids<{ id: number }>([{ id: "46100709" }]);
zemil
  • 3,235
  • 2
  • 24
  • 33