Platform: Property Payload does not exist on type...

Created on 26 May 2017  路  47Comments  路  Source: ngrx/platform

I know there has been discussions on this before, but with my current setup (see #29) I am still getting a ngc compilation error (TS 2.3.3), when using something like actions.payload:

Property 'payload' does not exist on type 'SettingActions'.

The selector from PHPStorm does also not recognize the payload:

bildschirmfoto 2017-05-26 um 14 39 41

Interesting is that it seems to take the type from the first action (which indeed does not have a payload constructor). Here is the corresponding action file:

....
export const LIST_SETTINGS: string = '[Settings] List';
export const LIST_SETTINGS_SUCCESS: string = '[Settings] List Success';

export class ListSettingAction implements Action {
    readonly type: string = LIST_SETTINGS;
}

export class ListSettingSuccessAction implements Action {
    readonly type: string = LIST_SETTINGS_SUCCESS;
    constructor(public payload: any[]) { }
}

....

export type SettingActions
    = ListSettingAction
    | ListSettingSuccessAction ....

Most helpful comment

+1. Please get payload attribute back to Action.

All 47 comments

Could you show the definition of the action with type UPDATE_SETTINGS_SUCCESS?

I can think of an scenario where this could happen: you defined UPDATE_SETTINGS_SUCCESS, but there is no action class that use it as type value.

@jotatoledo ... here is the entire action file:

import { Action } from '@ngrx/store';
import { SettingsPayload } from './settings.model';

export const LIST_SETTINGS: string = '[Settings] List';
export const LIST_SETTINGS_SUCCESS: string = '[Settings] List Success';
export const LIST_SETTINGS_FAILURE: string = '[Settings] List Failure';
export const UPDATE_SETTINGS: string = '[Settings] Update';
export const UPDATE_SETTINGS_SUCCESS: string = '[Settings] Update Success';
export const UPDATE_SETTINGS_FAILURE: string = '[Settings] Update Failure';
export const OPEN_DRAWER: string = '[Settings:Layout] Open Drawer';
export const CLOSE_DRAWER: string = '[Settings:Layout] Close Drawer';
export const SET_TITLE: string = '[Settings:Title] Set title';
export const SET_MODULE: string = '[Settings:Title] Set module';
export const GET_MODULE_TITLE: string = '[Settings:Title] Get module and title';

export class ListSettingAction implements Action {
    readonly type: string = LIST_SETTINGS;
}

export class ListSettingSuccessAction implements Action {
    readonly type: string = LIST_SETTINGS_SUCCESS;
    constructor(public payload: any[]) { }
}

export class ListSettingFailureAction implements Action {
    readonly type: string = LIST_SETTINGS_FAILURE;
    constructor(public payload: any) { }
}

export class UpdateSettingAction implements Action {
    readonly type: string = UPDATE_SETTINGS;
    constructor(public payload: any) { }
}

export class UpdateSettingSuccessAction implements Action {
    readonly type: string = UPDATE_SETTINGS_SUCCESS;
    constructor(public payload: SettingsPayload) { }
}

export class UpdateSettingFailureAction implements Action {
    readonly type: string = UPDATE_SETTINGS_FAILURE;
    constructor(public payload: SettingsPayload) { }
}

export class OpenDrawerAction implements Action {
    readonly type: string = OPEN_DRAWER;
}

export class CloseDrawerAction implements Action {
    readonly type: string = CLOSE_DRAWER;
}

export class SetTitleAction implements Action {
    readonly type: string = SET_TITLE;
}

export class SetModuleAction implements Action {
    readonly type: string = SET_MODULE;
}

export class GetModuleTitleAction implements Action {
    readonly type: string = GET_MODULE_TITLE;
}

export type SettingActions
    = ListSettingAction
    | ListSettingSuccessAction
    | ListSettingFailureAction
    | OpenDrawerAction
    | CloseDrawerAction
    | UpdateSettingAction
    | UpdateSettingSuccessAction
    | UpdateSettingFailureAction;

I have solved it for now by going back to the 2.x branch. When on v4, giving each class a constructor (which is quite annoying) also helps.

Also got this error.

Solved by adding the constructor in all my Actions.

You can add the constructor and assign null as default value:

constructor(public payload: any = null) { }

@loiane I have done similar (constructor(public payload?: any) { }), but at the end that cannot be the solution. When you have an action file with 10-20 actions of which only 5 need a payload, then we are talking about a lot redundancy.

you can create your CustomAction

export interface CustomAction extends Action {
type: string;
payload?: any;
}

and then reuse it

I am pretty sure that's because you are coercing your action type constants into strings.

Try changing all of the lines that look like this:

export const LIST_SETTINGS: string = '[Settings] List';

to this:

export const LIST_SETTINGS = '[Settings] List';

You basically want TypeScript to infer the type of LIST_SETTINGS as '[Settings] List' and not string.

I don't understand why payload property has been removed.

I was using methods instead of classes in actions.ts files, I created a custom Action classe to resolve it (solution provided by @elvirdolic), but I could not resolve TS errors in effects classes because 'payload' is not known :

.map(action => action.payload)

+1. Please get payload attribute back to Action.

What @MikeRyanDev suggested worked, but only when using the tslint rules from the example app, which in effect does not require you to set types for member variables. Is that not a little counter intuitive when it comes to TYPE-Script?
I am certainly no pro, and I get that TS can infer types. We had early on decided to type everything, just to make certain. This setup, as described above, would counter that. A payload type would probably help.

We can go on without it also, since we moved all the store logic out into a separat module which we link into the main app. That way we can use two different lint rules.

I think it makes a lot of sense to remove the payload property and have recently stopped using it in my apps and just using the action itself to be the payload. This makes actions more flexible and removes a lot of one-liners to get to the payload property. Now actions themselves more easily adhere to common data structures/DTOs in your app and makes writing reducers and reducer helper functions easier. For those that still find it useful, the approach advocated by @elvirdolic works just fine.

@axtho If you don't trust type inference, you can be explicit:

export const LIST_SETTINGS: '[Settings] List' = '[Settings] List';

I just find that to be too verbose.

@MikeRyanDev yeah. That's ugly :) So, from my standpoint this issue is resolved. Thanks also to @elvirdolic for your suggestion.

I followed Mike Ryan's suggestions and am using the tslint from the example app. Compiler, linter and IDE linting works fine now.

So it does not bother anyone to do this in Effects definitions?

.map(action => (action as any).payload)

(same as using toPayload method)

For me they look like this:

updateSetting$: Observable<Action> = this.actions$
        .ofType(actions.UPDATE_SETTINGS)
        .map((action: actions.UpdateSettingAction) => action.payload)

And I am fine with that ...

@craigsmitham How would set a variable in the store to some arbitrary value without the payload? For example say i need an action to set the username. So how can you handle SET_USER(payload: 'some_user') without passing the value as a payload?

@kemalcany What I do is derive a custom action from the Action interface that adds the fields I need. It's makes for a more "strongly-typed" way of creating the reducer functions:

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

export class MyState {
  fieldOne: string,
  fieldTwo: string
}

export class MyStateAction implements Action {
  type: string;
  fieldOne: string;
  fieldTwo: int;
}

export function myReducer(state: MyState, action: MyStateAction) {
  switch(action.type) {
    case 'something':
      return Object.assign({fieldOne: action.fieldOne, fieldTwo: action.fieldTwo})
  }
}

At first, it seemed like a hassle to write all this extra code, but I actually prefer it. More type safe or something...

You can also use the solution provided by @elvirdolic above. May be easier to get started with that one.

Also one can think about using ES6 destructuring assignment to get the type and the payload.

So instead of export const someReducer = (state: Model = {} as Model, action: Action) => {

you would write export const someReducer = (state: Model = {} as Model, {type, payload}) => {

Use heirdom:

export interface ActionWithPayloadInterface extends Action {
  payload: any;
}

export function counterReducer(state: PropertyDictionaryModel = null, action: ActionWithPayloadInterface) {

  switch (action.type) {

    case PropertyDictionaryActionsEnum.UPDATE_SUCCESS:
      return Object.assign({}, action.payload);

    default:
      return state;
  }
}

@elvirdolic probably a dumb question but new to Angular 2+ and TS -- where exactly do you implement this interface? I tried the following code:

import { CustomAction } from '../contracts/customAction.interface';
...
@Effect() login$: Observable<CustomAction> = this.actions$.ofType('LOGIN')

and I'm still getting the following error:
ERROR in /www/Symposia/they.usesymposia.com-ng2/tickets/src/app/effects/event.effects.ts (19,44): Property 'payload' does not exist on type 'Action'.

Thank you for your help!

If the LoginAction implements CustomAction, try this.action$.ofType<LoginAction>('LOGIN').
The type hint on Observable that you added only indicates what type login$ will be after all the operators are applied, and isn't relevant, probably, when you are trying to use payload.

Guys am going back to Redux

The removal of payload attribute from action is a huge step backward, making definition of actions now so burdensome.

@hieuxlu you can add it back with a simple interface definition that takes 10 seconds:

export interface PayloadAction {
   type: string;
   payload: any;
}

Not forcing it on users by default is advantageous and gives more flexibility and enables some cleaner patterns.

.map(action => (action as any).payload) will do the work

I don't understand, what was the logic behind removing payload attribute from Action interface? This is causing a lot of confusion, issues and compile time errors. I'm not a fan of this decision, an action is always type+payload, it's a written rule in Flux Architecture, look it up.

@avatsaev some methods do not require payload. for instance, toggle...()

@axtho try please if the following works correctly (i.e. usage of particular types in separate functions): https://github.com/marbug/angular2-and-typescript-and-redux/blob/master/from-scratch/quick-start-app/src/app/shared/page/page.reducer.ts

you must be kidding me action could be expressed as
interface IAction {
type: string;
payload?: any;
}
anw the way it is enforces making typings more explicit by specifying the specific action you filter by

@mcblum try

this.actions$.ofType(LOGIN)
    .mergeMap((action: LoginAction) ............

Then I am sure you will be able to access payload

the trick is in specifying your expected action types in the operation

@agalazis yeah man I got it figured out from the other comments. The thumbs down was a response to the "you must be kidding me" attitude. Certainly plenty of learning to do but you don't know until you know.

@mcblum yes mate noticed. It was just my first reaction even that was canceled out by the fact that it is the way it is no attitude intended or what so ever it was just me getting surprised about this issue. I wouldn't make fun of anything that tricked me as well. I should take care of what I write as it can be interpreted in many ways. "You" wasn't refering to anybody pecific(otherwise I would tag somebody), I was just surpised that its so simple to just expose it that way but yet they chose not to and there is a reason that's all.

An even easier fix is to override the @ngrx/store Action type with the following:

declare module '@ngrx/store' {
  interface Action {
    type: string;
    payload?: any;
  }
}

You can place that snippet anywhere in your project, and it'll stop whining about the payload not being present.

IMHO more simple way is to use own Action like:

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

export class MyAction implements Action {
  type: string;

  static prepareAction(params: any) {
    return new MyAction(params.type, params.payload);
  }

  constructor(type: string, public payload: any) {
    this.type = type;
  }

}

My solution:

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

export interface CustomAction<Payload> extends Action { 
  type: string;
  payload: Payload;
}
import { ofType } from '@ngrx/store';

export function ofTypeActionPayload<Payload>(...actionsTypes: string[]) {
  return ofType<CustomAction<Payload>>(...actionsTypes);
}

Simple example of usage:

interface ITodo {
  id: string;
  content: string;
}

@Effect()
  addTodo$ = this.actions$.pipe(
    ofTypeActionPayload<ITodo>('ADD_TODO_REQUEST'),
    mergeMap(({payload}) => this.http.get(`/api/todos/${payload.id}`).pipe(
      map(data => addTodo(data.content))
    ))
  );

I think the issue we face here is not the fact that the Action interface has changed but insufficient communication.
Working example with examples how to build actions now can be found here:
https://github.com/ngrx/platform/blob/master/example-app/app/auth/actions/auth.ts

@TeoTN Did you mentioned this to the NGRX devs?

So we need a custom Action type right? ya'll need the docs updated?

what i'm doing is this

interface CAction<C> {
  type: string;
  payload?: C
}

interface Creds {
  userName: string;
  password: string;
}

@Effect() login = this.actions$
  .ofType<CAction<Creds>>("LOGIN")
  // i get access to payload here
  .switchMap(action => action.payload)

I had these issues too (as I am the OP) and I followed the examples in the example app. Now it works fine, without custom Action.

The crux of the matter is that ofType needs the action supplied as a type parameter (as it is seen here). For the corresponding action look at the AddBook action here.

I am not sure if it has to do with the pipeable operators too. The examples (and my effects) use the pipe.

This setup has worked for me. Maybe it helps someone else too.

@abbasogaji I would strongly advice against using &, this will create an Action type that has all of the action's properties. Instead, type the action's property within the reducer and type the injected actions inside effects.

You can take a look at the example app for an example.

@timdeschryver ok thanks bro. i will take a look at ngrx effects, but is there any other suitable alternative without using ngrx effects?

I'm not sure what you're asking with

but is there any other suitable alternative without using ngrx effects?

How to define action (and action unions) and how to use them within reducers has no dependency on ngrx effects.

@timdeschryver

Instead, type the action's property within the reducer and type the injected actions inside effects.

sorry i did'nt get that earlier but is this what you meant?:
For Example:
typescript export function authReducer( state = initialState, action: AuthActions.AuthActions // defining the action's property type )
if this is what you meant, Yes i did that before, and in my AuthActions i exported a _union type_ "AuthActions" but it didn't work, it could only recognize "type" action property (within my reducer function)

see the following snippet:

1. my custom ngrx action (auth.action.ts)

````typescript
import { Action, ActionReducer } from '@ngrx/store';
import { User } from 'src/app/user/user.model';

export const LOGIN = 'LOGIN';
export const SIGNUP = 'SIGNUP';
export const LOGOUT = 'LOGOUT';
export const SET_TOKEN = 'SET_TOKEN';
export const SET_USER_DETAILS = 'SET_USER_DETAILS';

export class LogIn implements Action{
readonly type: string = LOGIN;
}

export class SignUp implements Action{
readonly type: string = SIGNUP;
}

export class LogOut implements Action{
readonly type: string = LOGOUT;
}

export class SetToken implements Action{
readonly type: string = SET_TOKEN;
}
export class SetUserDetails implements Action{
readonly type : string = SET_USER_DETAILS;
constructor(public payload: User){}
}

export type AuthActions = LogIn | SignUp | LogOut | SetToken | SetUserDetails
**2. my reducer (auth.reducer.ts):** typescript
import * as AuthActions from './auth.actions';
import { User } from 'src/app/user/user.model';

export interface State {
token : string,
authenticated : boolean,
user : User
}
const initialState : State = {
token : null,
authenticated : false,
user : {
name : null,
id : null,
imageUrl : null,
email : null,
status: null,
roles : null
}
}
export function authReducer(state = initialState, action : AuthActions.AuthActions){
switch(action.type){
case(AuthActions.LOGIN):
case(AuthActions.SIGNUP):
return {
...state,
authenticated : true,
}
case(AuthActions.LOGOUT):
return {
...state,
token: null,
authenticated: false,
user : {
name : null,
id : null,
imageUrl : null,
email : null,
status: null,
roles : null
}
}
case(AuthActions.SET_USER_DETAILS):
return {
...state,
user : {...action.payload} //payload is not recognized as a property of action
}
default:
return state;

}

}
````

But For some reason its seeing it as an error
error TS2339: Property 'payload' does not exist on type 'AuthActions'. Property 'payload' does not exist on type 'LogIn'.

And i tried running the example code but am still getting that error unless i use "&"(intersect types)

See #951 @abbasogaji - more specific https://github.com/ngrx/platform/issues/951#issuecomment-425190274

@timdeschryver thankssss for the link....it worked like a charm.
I also tried debugging my previous code in the above comment, and i surprisingly figured out the problem at last

The problem was actually in my code. I was defining the type of action's "type" property in my CustomActions classes, i was only supposed to assign values to it since its a property of the Interface "Action".
In a nut shell, the error occurred because;
I did this
typescript export class ACustomAction implements Action { readonly type : string = VALUE //error occured cause i delared type's type as string }
instead of this
typescript export class ACustomAction implements Action { readonly type = VALUE }

You can access your specific action constructor iff you make your Action in reducer Typed.
You dont even need payload anymore...
This is the reducer file
image
this is the actions file
image

Was this page helpful?
0 / 5 - 0 ratings

Related issues

brandonroberts picture brandonroberts  路  3Comments

doender picture doender  路  3Comments

smorandi picture smorandi  路  3Comments

hccampos picture hccampos  路  3Comments

dollyshah-02-zz picture dollyshah-02-zz  路  3Comments