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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With