cartant/ts-action

Cannot find name when using action as type in method

Silthus opened this issue ยท 5 comments

I am trying to use this awesome library with @ngxs/store. Everything works fine except the type signature on action methods. When trying to use a generated ts-action action Typescript shows the compile time error: Cannot find name 'ActionName'.

See this stackblitz as demo: https://stackblitz.com/edit/angular-azg9at

import { State, StateContext, Action, Selector } from '@ngxs/store';
import { action, payload } from 'ts-action';

export const CreateModelAction = action('[App] CREATE MODEL', payload<AppModel>());

export interface AppModel {
  id: number;
  name?: string;
}

export interface AppStateModel {
  models: AppModel[];
}

const DEFAULT_STATE: AppStateModel = {
  models: []
};

@State({
  name: 'app',
  defaults: DEFAULT_STATE
})
export class AppState {

  @Selector()
  static models(state: AppStateModel) {
    return state.models;
  }

  @Action(CreateModelAction)
  createModel(ctx: StateContext<AppStateModel>, action: CreateModelAction) { // <-- this is the error here
    ctx.patchState({
      models: [
        ...ctx.getState().models,
        action.payload
      ]
    })
  }
}

The actions created by the action function are classes/constructors that are created at runtime, so there is no corresponding TypeScript type.

To use the action where you'd usually use a TypeScript type, you will need to do what's done in the README with reducer parameters. That is, you'll need to use union and typeof, like this:

import { action, payload, union } from 'ts-action';

...

export const CreateModelAction = action('[App] CREATE MODEL', payload<AppModel>());
const CreateModelActionUnion = union({ CreateModelAction });
export type CreateModelActionType = typeof CreateModelActionUnion;

...

@Action(CreateModelAction)
createModel(ctx: StateContext<AppStateModel>, action: CreateModelActionType) { ... }

union is implemented using an internal/undocumented action property on the constructor, so that would be the safest way to go. You could also use the action property directly, but it's possible that might be removed if/when I update the package to support a minimum TypeScript version of 2.8 (that version has a bunch of new features that should simplify the implementation of ts-action):

export const CreateModelAction = action('[App] CREATE MODEL', payload<AppModel>());
export type CreateModelActionType = typeof CreateModelActionUnion.action;

Actually, if you are using TypeScript 2.8, the best way to solve the problem is with the new, built-in InstanceType type:

export const CreateModelAction = action('[App] CREATE MODEL', payload<AppModel>());
export type CreateModelActionType = InstanceType<typeof CreateModelActionUnion>;

...

@Action(CreateModelAction)
createModel(ctx: StateContext<AppStateModel>, action: CreateModelActionType) { ... }

Would it be possible to make action return the InstanceType so it would only require one single line to make it all work?

I don't think so, action has to return a constructor - not the constructed type.

Anyway, there are some things that are much easier now, with TypeScript 2.8, so revisiting the implementation is on my todo list (to which I'll add a reference to your question). It's quite likely there will be some simplification (and breaking changes).

Closing this. I'll revisit your question when I get around to the TypeScript 2.8 refactor.