Actions/state to load data from backend
Asked Answered
H

2

26

I've just started experimenting with ngxs but from my reading so far I'm not 100% clear on where I should be calling back to my API to persist and read data (all examples I've seen are either not doing it, or using some mock).

E.g. I've created a state where I maintain a list of items. When I want to add an item, I dispatch the 'AddItem` action to the store, where I add that new item to the state. This all works ok - the question is where is the appropriate place to plug in the call that POSTs the item to the server?

Should I call the API in my action implementation i.e. just before I update the store's item list.

Or should I call the API in my Angular component (via a service), then dispatch the 'Add Item' action when I received a response?

I'm quite new to this area, so any guidance or pros/cons of these approaches would be great.

Handgrip answered 23/4, 2018 at 6:43 Comment(0)
L
21

The best place is in your action handler.

import { HttpClient } from '@angular/common/http';
import { State, Action, StateContext } from '@ngxs/store';
import { tap, catchError } from 'rxjs/operators';

//
// todo-list.actions.ts
//
export class AddTodo {
  static readonly type = '[TodoList] AddTodo';
  constructor(public todo: Todo) {}
}


//
// todo-list.state.ts
//
export interface Todo {
  id: string;
  name: string;
  complete: boolean;
}
​
export interface TodoListModel {
  todolist: Todo[];
}
​
@State<TodoListModel>({
  name: 'todolist',
  defaults: {
    todolist: []
  }
})
export class TodoListState {

  constructor(private http: HttpClient) {}
​
  @Action(AddTodo)
  feedAnimals(ctx: StateContext<TodoListModel>, action: AddTodo) {

    // ngxs will subscribe to the post observable for you if you return it from the action
    return this.http.post('/api/todo-list').pipe(

      // we use a tap here, since mutating the state is a side effect
      tap(newTodo) => {
        const state = ctx.getState();
        ctx.setState({
          ...state,
          todolist: [ ...state.todolist, newTodo ]
        });
      }),
      // if the post goes sideways we need to handle it
      catchError(error => window.alert('could not add todo')),
    );
  }
}

In the example above we don't have a explicit action for the return of the api, we mutate the state based on the AddTodo actions response.

If you want you can split it into three actions to be more explicit,

AddTodo, AddTodoComplete and AddTodoFailure

In which case you will need to dispatch new events from the http post.

Laurettelauri answered 23/4, 2018 at 7:21 Comment(3)
Thanks for the quick reply - I'll try your example here against my app. Similarly, I assume we'd define a 'LoadTodos' action, and have that action handler load the initial data from the server (as the default state will be empty). Does that sound correct?Handgrip
You can use the NgxsOnInit lifecycle event to dispatch a LoadTodos action. and then add an action handler for that. It's in the documentation how you do that.Laurettelauri
Got it - I see that now under the Advanced -> LifeCycle part of the documentation (admittedly I hadn't read everything under Advanced yet). Thanks again for the help, really liking what I'm seeing so far with NGXSHandgrip
N
4

If you want to separate the effect from the store you could define a base state class:

@State<Customer>( {
    name: 'customer'
})
export class CustomerState {
    constructor() { }

    @Action(ChangeCustomerSuccess)
    changeCustomerSuccess({ getState, setState }: StateContext<Customer>, { payload }: ChangeCustomerSuccess ) {
        const state = getState();
       // Set the new state. No service logic here.
       setState( {
           ...state,
           firstname: payload.firstname, lastname: lastname.nachname
       });
    }
}

Then you would derive from that state and put your service logic in the derived class:

@State<Customer>({
    name: 'customer'
})
export class CustomerServiceState extends CustomerState {

    constructor(private customerService: CustomerService, private store: Store) {
        super();
    }

    @Action(ChangeCustomerAction)
    changeCustomerService({ getState, setState }: StateContext<Customer>, { payload }: ChangeCustomerAction) {

        // This action does not need to change the state, but it can, e.g. to set the loading flag.
        // It executes the (backend) effect and sends success / error to the store.

        this.store.dispatch( new ChangeCustomerSuccess( payload ));
    }
}

I have not seen this approach in any of the NGXS examples I looked through, but I was looking for a way to separate those two concerns - UI and backend.

Nightshade answered 24/4, 2018 at 14:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.