Search code examples
typescriptngrx

How to properly use generics with ngrx actions


I'm working on a Angular 7 app that uses the NGRX store. We have a lot of entities, and almost each one has a dedicated list view. I wanted to try out generics with NGRX store, so I would not need to write almost identical selectors, actions, reducers etc. I started with actions and created a function that would return me a set of them, but I cannot get it to work.

    // For type safety
    enum AddTypeNames {
      ADD_REQUEST = 'AddRequestAction',
      ADD_SUCCESS = 'AddSuccessAction',
      ADD_FAILURE = 'AddFailureAction',
    }
    // Creates an object literal with a string key and Action as the value
    function createAddActions<T>(featureName: string): { [key in AddTypeNames]: Action } {
      class AddRequestAction implements Action {
        readonly type = `[${featureName}] Add request`;
        constructor(payload: { entity: T }) { }
      }
      class AddSuccessAction implements Action {
        readonly type =  `[${featureName}] Add success`;
        constructor(payload: { entity: T }) { }
      }
      class AddFailureAction implements Action {
        readonly type =  `[${featureName}] Add error`;
        constructor(payload: { error: HttpErrorResponse }) { }
      }
      return {
        AddRequestAction,
        AddSuccessAction,
        AddFailureAction,
      };
    }

But it's showing an error in the editor saying:

(property) AddRequestAction: Action
Property 'type' is missing in type 'typeof AddRequestAction' but required in type 'Action'.ts(2741)

I've no idea what this error really means, as clearly I do have the type in the class... Am I even doing this the right way? What would I need to do to create actions with generic types? Is it actually considered a good practice to use generics as much as possible?


Solution

  • Compilation error existing because you declared that createAddActions will return dict od Action { [key in AddTypeNames]: Action }. But this syntax means object of instances of class Action, not a classes.

    So, you told TS that your return object values contains attribute 'type', your classes has not static attribute type.

    If you want to declare that result of functions is object with classes (not instances of it use this syntax: { new (...args): Action }.

    So:

    function createAddActions<T>(featureName: string): { [key in AddTypeNames]: { new (...args): Action } } {
    

    will not throws compilation error.

    Playground