NGXS Store is a state management solution for Angular apps that helps us build applications by working around our app’s data (state). In this tutorial, we’re gonna look at how to work with NGXS Store, custom Actions, handle dispatched Actions with @Action
and @Select
/@Selector
decorator. Then we will practice to understand all of them in a simple practical Angular 6 example.
Related Posts:
– NgRx: Angular 6 NgRx Store example – Angular State Management
– State Management with Redux: Introduction to Redux – a simple practical Redux example
– Reactive Streams:
- Introduction to RxJS – Extensions for JavaScript Reactive Streams
- Angular 4 + Spring WebFlux + Spring Data Reactive MongoDB example | Full-Reactive Angular 4 Http Client – Spring Boot RestApi Server
NGXS Store to manage App State
Why we need a State container
State container helps JavaScript applications to manage state.
=> Whenever we wanna read the state, look into only one single place – NGXS Store.
=> Managing the state could be simplified by dealing with simple objects and pure functions.
NGXS Store
Store holds the current state so that we can see it as a single source of truth for our data.
– access state using store.select(property)
or @Select
/@Selector
decorator.
– update state via store.dispatch(action)
.
export class CustomerStateModel { readonly customers: Customer[]; } @State({ name: 'customers', defaults: { customers: [] } }) export class CustomerState { @Selector() static getCustomers(state: CustomerStateModel) { return state.customers; } } // component import { Store, Select } from '@ngxs/store'; import { Observable } from 'rxjs'; export class CustomersListComponent { @Select(CustomerState.getCustomers) customers: Observable ; // customers: Observable ; constructor(private store: Store) { // this.customers = this.store.select(state => state.customers.customers); } }
@Select()
decorator is the way to slice data from the store. We can use it comfortably:
@Select() customer; @Select(state => state.customer.age) age; @Select(CustomerState.getCustomers) customers; @Selector() // slicing logic static getCustomers(state: CustomerStateModel) { return state.customers; }
Action
Action is payload of information that is sent to Store using store.dispatch(action)
.
Action must have a type property that should typically be defined as string constants. It indicates the type of action being performed:
export const CREATE_CUSTOMER = 'Customer_Create'; export const DELETE_CUSTOMER = 'Customer_Delete'; export class CreateCustomer { static readonly type = CREATE_CUSTOMER; constructor(public payload: Customer) { } } export class DeleteCustomer { static readonly type = DELETE_CUSTOMER; constructor(public id: string) { } }
Handle dispatched Actions
NGXS gives us a StateContext
object to mutate the store in response to an Action. We don’t need to separate this logic in a Reducer and/or Action stream.
export class CustomerState { @Action(CreateCustomer) save(context: StateContext, action: CreateCustomer) { const state = context.getState(); context.patchState({ customers: [...state.customers, action.payload] }); } @Action(DeleteCustomer) remove(context: StateContext , action: DeleteCustomer) { const state = context.getState(); context.patchState({ customers: state.customers.filter(({ id }) => id !== action.id) }); } }
We use getState()
to get the current state, setState()
to return the state.
In this example, we use patchState()
to make updating the state easier: only pass it the properties we wanna update on the state and it handles the rest.
Practice
Example overview
This is a simple Angular 6 NGXS Application that has:
– CustomerStateModel
(customer.state.ts) as the main state that is stored inside NGXS Store.
– 2 types of Action: CREATE_CUSTOMER
and DELETE_CUSTOMER
(customer.actions.ts).
– Dispatched Actions will be handled with @Action
decorator (customer.state.ts).
We can save/remove Customer. App will update UI immediately.
>> Click on Delete button from any Customer:
Step by step
Install NGXS Store
Install NGXS Store
Run cmd: npm install @ngxs/store
.
Create Data Model
app/customers/models/customer.ts
export class Customer { id: string; name: string; age: number; active: boolean; constructor(id?: string, name?: string, age?: number, active?: boolean) { this.id = id; this.name = name; this.age = age; this.active = active; } }
Create Actions
app/actions/customer.actions.ts
import { Customer } from '../customers/models/customer'; export const CREATE_CUSTOMER = 'Customer_Create'; export const DELETE_CUSTOMER = 'Customer_Delete'; export class CreateCustomer { static readonly type = CREATE_CUSTOMER; constructor(public payload: Customer) { } } export class DeleteCustomer { static readonly type = DELETE_CUSTOMER; constructor(public id: string) { } }
Create State & handle dispatched Action
app/state/customer.state.ts
import { State, Action, StateContext, Selector } from '@ngxs/store'; import { Customer } from '../customers/models/customer'; import { CreateCustomer, DeleteCustomer } from '../actions/customer.actions'; export class CustomerStateModel { readonly customers: Customer[]; } @State({ name: 'customers', defaults: { customers: [ { id: '1', name: 'Andrien', age: 27, active: true } ] } }) export class CustomerState { @Selector() static getCustomers(state: CustomerStateModel) { return state.customers; } @Action(CreateCustomer) save(context: StateContext , action: CreateCustomer) { const state = context.getState(); context.patchState({ customers: [...state.customers, action.payload] }); } @Action(DeleteCustomer) remove(context: StateContext , action: DeleteCustomer) { const state = context.getState(); context.patchState({ customers: state.customers.filter(({ id }) => id !== action.id) }); } }
Import NGXS Store
app/app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { NgxsModule } from '@ngxs/store'; import { AppComponent } from './app.component'; import { CustomerState } from './state/customer.state'; import { CreateCustomerComponent } from './customers/create-customer/create-customer.component'; import { CustomerDetailsComponent } from './customers/customer-details/customer-details.component'; import { CustomersListComponent } from './customers/customers-list/customers-list.component'; @NgModule({ declarations: [ AppComponent, CreateCustomerComponent, CustomerDetailsComponent, CustomersListComponent ], imports: [ BrowserModule, NgxsModule.forRoot([ CustomerState ]), ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Create Components
Create Customer Component
customers/create-customer/create-customer.component.ts
import { Component, OnInit } from '@angular/core'; import { Store } from '@ngxs/store'; import { CreateCustomer } from '../../actions/customer.actions'; @Component({ selector: 'app-create-customer', templateUrl: './create-customer.component.html', styleUrls: ['./create-customer.component.css'] }) export class CreateCustomerComponent implements OnInit { constructor(private store: Store) { } ngOnInit() { } saveCustomer(id, name, age) { this.store.dispatch(new CreateCustomer( { id: id, name: name, age: age, active: false } )); } }
customers/create-customer/create-customer.component.html
Create Customers
Customer Details Component
customers/customer-details/customer-details.component.ts
import { Component, OnInit, Input } from '@angular/core'; import { Store } from '@ngxs/store'; import { DeleteCustomer } from '../../actions/customer.actions'; import { Customer } from '../models/customer'; @Component({ selector: 'app-customer-details', templateUrl: './customer-details.component.html', styleUrls: ['./customer-details.component.css'] }) export class CustomerDetailsComponent implements OnInit { @Input() customer: Customer; constructor(private store: Store) { } ngOnInit() { } removeCustomer(id) { this.store.dispatch(new DeleteCustomer(id)); } }
customers/customer-details/customer-details.component.html
{{customer.name}}{{customer.age}}
Customers List Component
customers/customers-list/customers-list.component.ts
import { Component, OnInit } from '@angular/core'; import { Store, Select } from '@ngxs/store'; import { Observable } from 'rxjs'; import { Customer } from '../models/customer'; import { CustomerState } from '../../state/customer.state'; @Component({ selector: 'app-customers-list', templateUrl: './customers-list.component.html', styleUrls: ['./customers-list.component.css'] }) export class CustomersListComponent implements OnInit { @Select(CustomerState.getCustomers) customers: Observable; // customers: Observable ; constructor(private store: Store) { // this.customers = this.store.select(state => state.customers.customers); } ngOnInit() { } }
customers/customers-list/customers-list.component.html
Customers
Import Components to App Component
app/app.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'ozenero'; description = 'NGXS Example'; }
app/app.component.html
{{title}}
{{description}}