Search code examples
angulartypescriptangular-akitaakita

Appropriate way to implement the base abstract state with Akita


I've tried to implement such a thing and I'm a bit confused. Suppose I need to implement variety of grid based page which designed more or less the same way, but each of them has some peculiarities. That's why I need some base abstract store/query services where I am planning to implement common logic.

Each grid will operate with child of BaseLineItem:

export interface BaseListItem {
  Id: number;
  StatusId: number;
  // Other properties
}

That the way I try to implement it

export interface PageState<TListItem extends BaseListItem> extends EntityState<TListItem> {
  // some state properties
}

export abstract class PageStore<TListItem extends BaseListItem, TState extends PageState<TListItem>> extends EntityStore<TState> {
  constructor(initialState: Partial<TState>) {
    super(initialState);
  }

  addNewItems(items: TListItem[]): void {
    this.upsertMany(items);
  }
}

It's supposed that for each form I will implement children for line item, state and store, so I can have possibilities to add some peculiarities which are specific for each form. But in that place I've faced with a problem in addNewItems method - upsertMany, which shows the following error:

Argument of type 'TListItem[]' is not assignable to parameter of type 'getEntityType<TState>[]'.\n  Type 'TListItem' is not assignable to type 'getEntityType<TState>'.\n    Type 'BaseListItem' is not assignable to type 'getEntityType<TState>'.

It seems that Akita are not able to infer the type for entity.

That is the question. How to solve this problem, without using as any everywhere(If I start to do it in the very base level, I will lost intellisense across the source code)? I came to the front-end world from the .Net background. Maybe I don't understand something and this architecture pattern isn't the best here? And I need some advice.


Solution

  • EntityStore.upsertMany method takes as a parameter array of EntityType[] which was inferred by using typescript helper type

    export class EntityStore<S extends EntityState = any, EntityType = getEntityType<S>,
                                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    Here's the source code of this helper:

    export type getEntityType<S> = S extends EntityState<infer I> ? I : never;
                   
    

    Constrained generics, which you use in your implementation, confuse that helper helper a little and it can't infer correct type and returns unknown which doesn't match with your generic type.

    Maybe TypeScript will infer types better in future for such cases but for now a simple solution would be using the same Akita helper in your code instead of TListItem[]:

    addNewItems(items: getEntityType<TState>[]): void {
      this.upsertMany(items);
    }
    

    You can test it in Playground

    Below is an example of derived class where you can observe correct type inference.

    enter image description here

    Also, you can take a look at similar issue in Akita issue tracker