1
votes

In my angular 2 I use ngrx and have some actions and reducers. Example of actions :

import { Action } from '@ngrx/store';

export const actionTypes = {
  ACTION_1: type('Actions 1'),
  ACTION_2: type('Actions 2'),
};

export class ActionOne implements Action {
  public type = actionTypes.ACTION_1;

  constructor(public payload: any) { }
}

export class ActionTwo implements Action {
  public type = actionTypes.ACTION_2;
}

export type Actions
  = ActionOne
  | ActionTwo;

So, some actions has payload, others - no, and Actions is union type, which can be ActionOne or ActionTwo. But in me reducer I have an error: Property 'payload' does not exist on type 'Actions' Property 'payload' does not exist on type 'ActionTwo'.

Reducer is like this:

export function reducer(state = initialState, action: Actions): IState {
  switch (action.type) {

    case actions.actionTypes.ACTION_1: {
      return Object.assign({}, state, {
        data: action.payload,
      });
    }

    case ...
  }
}

I got this error after updating typescript version from 2.0.3 to 2.2.2. So, is there way to fix error without putting payload to every action? May be is there some option from tsconfog.json for this case?

2

2 Answers

2
votes

You could declare the constants in a namespace, instead of a dictionary. This allows ACTION_1 and ACTION_2 take the literal type, which is essential for discrimated union to work.

export namespace actionTypes {
    export const ACTION_1 = 'Action 1';  // <-- type of ACTION_1 is 'Action 1' in TS 2.1+
    export const ACTION_2 = 'Action 2';
};

The type of each class needs to be a constant, otherwise the type of type would be string instead of literal type.

export class ActionOne implements Action {
    public readonly type = actionTypes.ACTION_1;   // <-- note the `readonly`
    constructor(public payload: any) { }
}

export class ActionTwo implements Action {
    public readonly type = actionTypes.ACTION_2;
}

The ACTION_ONE: type('Action one') pattern has been deprecated by ngrx developers since TypeScript 2.1 / Angular 4. See https://github.com/ngrx/example-app/pull/88#issuecomment-272623083 for information.

-1
votes

You know inside this switch case which action it is so you can cast to the appropriate one:

case actions.actionTypes.ACTION_1: {
    return Object.assign({}, state, {
        data: (action as ActionOne).payload,
    });
}

The compiler is obviously right as the union results in a type that has only the shared properties, and payload isn't one of them.