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
ngxs.io

Medium
Thanks


NGXS: Redux implemented in 2018
By Gerard Sans
NGXS: Redux implemented in 2018
In this talk, we are going to look into NGXS, a new State Management library released earlier this year. We will introduce its main concepts and cover all the good stuff up to v3.1 release: state/actions life cycle, advanced selectors, snapshots, plugins and more!
- 3,573