Search code examples
angularreduxangular-redux

How to display items from redux store in angular5 app?


I have an angular5 component that I am trying to hookup with a reduxstore, it looks like this:

import { Component } from '@angular/core';
import { NgRedux, select, DevToolsExtension } from '@angular-redux/store';
import { TodoActions } from './actions';
import { AppState, INITIAL_STATE, rootReducer } from './store';
import {Observable} from "rxjs/Observable";

@Component({
  selector: 'app-root',
  template: `    
    <input type="text" #edit />
    <button (click)="actions.add({data:edit,action:'ADD'})">add</button>
    <p>The list is:</p>
    <ul>
      <li *ngFor="let item of (items | async)">
        {{item}}
      </li>
    </ul>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
  @select() readonly items$: Observable<string[]>;

  constructor(
    ngRedux: NgRedux<AppState>,
    devTools: DevToolsExtension,
    private actions: TodoActions) {

    ngRedux.configureStore(
      rootReducer,
      INITIAL_STATE,
      null,
      devTools.isEnabled() ? [ devTools.enhancer() ] : []);
  }
}

The issue is that I cannot display items in the list. Here is the rootreducer:

import {Action} from 'redux';
import {TodoActions} from './actions';

export interface AppState {
  items: string[];
}
export const INITIAL_STATE: AppState = {items: []};

export function rootReducer(state: AppState, action: Action): AppState {
  switch (action.type) {
    case TodoActions.ADD:
      var newstate = state;
      newstate.items.push(action.data.data.value);
      return newstate;
  }
  ;

default:
  return state;
}
}

How can I display the items? it looks like the first item gets added to the state from the redux-console. Here is also a githublink


Solution

  • This doesn't look quite right:

      var newstate = state;
      newstate.items.push(action.data.data.value);
      return newstate;
    

    You're not actually creating a new array or a copy: both newstate and state are the same array, so you're probably better off returning a new array shallow-cloned:

    export function rootReducer(state: AppState, action): AppState {
      switch (action.type) {
        case TodoActions.ADD:
          return {
            ...state,
            items: [
              ...state.items,
              action.data.value
            ]
          }
    
        default:
          return state;
      }
    }
    

    You might notice I've pulled the type specifier off action. I'm not sure it makes sense to specify Action as the type for action, in most sample code I've seen that passed untyped so:

    export function rootReducer(state: AppState, action): AppState {
    

    This will avoid TS complaining about lack of data on type Action. Alternatively, you could define a custom action type for each reducer you write.

    Once you get past that, I think the reason you're not seeing any items is because you name the array items$ in the @select decorator. So:

    @Component({
      selector: 'app-root',
      template: `    
        <input type="text" #edit />
        <button (click)="actions.add({value:edit.value,action:'ADD'})">add</button>
        <p>The list is:</p>
        <ul>
          <li *ngFor="let item of (items$ | async)">
            {{item}}
          </li>
        </ul>
      `,
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      title = 'app';
      @select() readonly items$: Observable<string[]>;
    

    works better. Note I tweaked the button definition a bit to avoid passing the entire input through, since you only need the value.