by Gerard Sans | @gerardsans

Google Developer Expert

Google Developer Expert

International Speaker

Spoken at 92 events in 25 countries

Blogger

Blogger

Community Leader

900

1.5K

Trainer

Master of Ceremonies

Master of Ceremonies

NGXS

Overview

  • Angular Developer Friendly
    • ​​Decorators
    • Dependency Injection
    • Life cycle hooks
  • Minimal Setup
  • Extensible via plugins
  • Community driven

Components

Actions

State

Mutate

Dispatch

Notify

Observables

actions

states

A

A

A

S

S

S

Components

Actions

State

Mutate

Dispatch

Notify

Plugins

Actions

State

Counter

Increment

Decrement

Reset

Total

Components

Actions

State

Mutate

Dispatch

Notify

import { State } from '@ngxs/store';

@State<number>({ 
  name: 'counter', 
  defaults: 0
})
export class CounterState { }
src/app.component.ts

Counter State

app/store/counter.state.ts

Components

Actions

State

Mutate

Dispatch

Notify

export class Increment {
  static readonly type = "[Counter] Increment";
}

// const action = new Increment(); 

// { 
//    type: "[Counter] Increment"
// }
src/app.component.ts

Increment Action

app/store/counter.actions.ts
import { State, Action, StateContext } from '@ngxs/store';
import { Increment } from '../store/counter.actions';

@State<number>({ name: 'counter', defaults: 0 })
export class CounterState {
  @Action(Increment)
  Increment(store: StateContext<number>) {
    const counter = store.getState();
    store.setState(counter+1);
  } 
}
src/app.component.ts

Increment Action

app/store/counter.state.ts
export class Reset {
  static readonly type = "[Counter] Reset";
  constructor(public payload: { value: number }) { }
}
// const action = new Reset({ value: 0 });
 
// { 
//   type: "[Counter] Reset", 
//   payload: { 
//     value: 0 
//   } 
// }
src/app.component.ts

Reset Action

app/store/counter.actions.ts
import { State, Action, StateContext } from '@ngxs/store';
import { Reset } from '../store/counter.actions';

@State<number>({ name: 'counter', defaults: 0 })
export class CounterState {
  @Action(Reset)
  Reset(store: StateContext<number>, action: Reset) {
    const counter = store.getState();
    store.setState(action.payload.value);
  }
}
src/app.component.ts

Reset Action

app/store/counter.state.ts

Components

Actions

State

Mutate

Dispatch

Notify

@Component({
  template: `
    <app-counter
      (increment)="increment()" (decrement)="decrement()"
      (reset)="reset()">
    </app-counter>`
})
export class AppComponent  {
  constructor(private store: Store) { }
  increment = () => this.store.dispatch(new Increment());
  decrement = () => this.store.dispatch(new Decrement());
  reset = () => this.store.dispatch(new Reset({ value: 0 }));
}
src/app.component.ts

Dispatching Actions

app/app.component.ts
@Component({
  template: `
    <app-counter [total]="counter|async"></app-counter>`
})
export class AppComponent  {
  @Select(state => state.counter) counter: Observable<number>;
}
src/app.component.ts

Subscribing to the Store

app/app.component.ts
@Component({ 
  selector: 'app-counter',
  template: `
    <div class="total">{{total}}</div>
    <button (click)="increment.emit()">+</button>
    <button (click)="decrement.emit()">-</button>
    <button (click)="reset.emit()">C</button>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
  @Input() total: number;
  @Output() increment = new EventEmitter();
  ...
}
src/app.component.ts

Counter

app/counter/counter.component.ts

Plugins

Logger

DevTools

Storage

Forms

WebSockets

Router

import { NgxsModule } from '@ngxs/store';
import { NgxsReduxDevtoolsPluginModule } 
  from '@ngxs/devtools-plugin';
​
@NgModule({
  imports: [
    NgxsModule.forRoot([]),
    NgxsReduxDevtoolsPluginModule.forRoot()
  ]
})
export class AppModule {}
src/app.component.ts

Adding DevTools Plugin

import { NgxsModule } from '@ngxs/store';
import { NgxsFormPluginModule } 
  from '@ngxs/form-plugin';
​
@NgModule({
  imports: [
    NgxsModule.forRoot([]),
    NgxsFormPluginModule.forRoot(),
    NgxsReduxDevtoolsPluginModule.forRoot()
  ]
})
export class AppModule {}
src/app.component.ts

Adding Forms Plugin

​@State({
  name: "app",
  defaults: {
    form: {
      model: undefined,
      dirty: false,
      status: "",
      errors: {}
    }
  }
})
export class AppState {}
src/app.component.ts

Adding initial form state

@Component({
  selector: 'form',
  template: `
    <form ngxsForm="app.form" novalidate
      [formGroup]="form" (ngSubmit)="onSubmit()">
      <input type="text" formControlName="email" />
      <button type="submit">Subscribe</button>
    </form>`
})
export class FormComponent { }
src/app.component.ts

Tracking form changes

import { NgxsModule } from '@ngxs/store';
import { NgxsRouterPluginModule } 
  from '@ngxs/router-plugin';
​
@NgModule({
  imports: [
    NgxsModule.forRoot([]),
    NgxsRouterPluginModule.forRoot()
  ]
})
export class AppModule {}
src/app.component.ts

Adding Router Plugin

Advanced NGXS

Todo App

Selectors

{ 
  todos: [{ id: 1, text: 'todo 1', complete: false }],
  currentFilter: "SHOW_ALL"
}

// Filtered Todos
[{ id: 1, text: 'todo 1', complete: false }]
src/app.component.ts

Example: Todos State Selector

{ 
  todos: [{ id: 1, text: 'todo 1', complete: false }],
  currentFilter: "SHOW_COMPLETED"
}

// Filtered Todos
[]
import { FilterState } from '../filter/filter.state';

@State<Todo[]>({ name: 'todos', defaults: [] })
export class TodosState {
  @Selector([FilterState]) static todos(state, currentFilter) {
    let t = state.slice().reverse();
    switch (currentFilter) {
      case 'SHOW_ACTIVE': return t.filter(t => !t.completed);
      case 'SHOW_COMPLETED': return t.filter(t => t.completed);
      case 'SHOW_ALL':
      default: return t;
    };
  }
}
src/app.component.ts

Selector: Filtered Todos

Comparison

  • v3.1.3 (3 months)
  • 53 contributors
  • Plugins
  • 1K stars
  • v6.0.1 (2 years)
  • 119 contributors
  • CLI integration
  • 3K stars

More

Austin McDaniel

@amcdnl

ngxs.io

Medium

Thanks