Why nobody told
me about ngrx/entity ?
Gerard Sans | @gerardsans
Google Developer Expert
Google Developer Expert
International Speaker
Spoken at 65 events in 23 countries
Blogger
Blogger
Trainer
Trainer
Redux
State Management
ngrx
Redux
Dan Abramov
Redux Principles
- Unidirectional data flow
- Single Store
- No side-effects
Unidirectional data flow
source: blog
1
2
3
4
5
Developer
Experience
extension.remotedev.io
Features
- Save/Restore State
- Live Debugging
- Time travel
- Dispatch Actions
Todo App
demo
ngrx
v5
What's new in v5?
- @ngrx/schematics (@angular-devkit)
- 'Pipeable' select operator (lettable)
- Support custom createSelector
- RxJS 5.5
- UpsertOne/Many
Rob Wormald
-
Suite including
- @ngrx/store*
- @ngrx/effects
- @ngrx/router-store
- @ngrx/store-devtools*
- @ngrx/entity*
- @ngrx/schematics*
- Re-implementation of Redux on top Angular and RxJS 5.
-
Colocation Support
- StoreModule.forRoot()
- StoreModule.forFeature()
- Payload Type checking
- Asynchronous Actions Middleware
- Executes side-effects
-
Colocation Support
- EffectsModule.forRoot()
- EffectsModule.forFeature()
Asynchronous Actions
source: blog
1
2
3
4
5
A
A
ngrx/entity
- Library to manage Lists
-
High performance
- EntityState<T>
- Ids lookup and Entities Map
-
Reducer Helper (EntityAdapter)
- getInitialState
- Add/update/remove: All, One, Many
- getSelectors: Ids, Entities, All, Total
- Scaffolding templates for ngrx
-
Provides commands for
- Setting initial Store and Effects
- Actions, Reducers, Entities, Features
- Containers + Store injected
- Automates creation + registration
ngrx/schematics commands
> ng new ngrx-entity-todo
> npm install @ngrx/schematics --save-dev
> npm install @ngrx/{store, effects, entity, store-devtools} --save
> ng generate store State --root --module app.module.ts --collection @ngrx/schematics
> ng generate effect App --root --module app.module.ts --collection @ngrx/schematics
> ng generate entity Todo -m app.module.ts --collection @ngrx/schematics
Filter Actions
// src/app/reducers/currentFilter/currentFilter.actions.ts
import { Action } from '@ngrx/store';
export enum CurrentFilterActionTypes {
SetCurrentFilter = '[Filter] Set current filter'
}
export class SetCurrentFilter implements Action {
readonly type = CurrentFilterActionTypes.SetCurrentFilter;
constructor(public payload: { filter: string }) {}
}
export type CurrentFilterActions = SetCurrentFilter;
Filter Reducer
// src/app/reducers/currentFilter/currentFilter.reducer.ts
export const initialState: string = 'SHOW_ALL';
export function reducer(
state = initialState,
action: CurrentFilterActions): string
{
switch (action.type) {
case CurrentFilterActionTypes.SetCurrentFilter:
return action.payload.filter
default:
return state;
}
}
Todos Actions
// src/app/reducers/todo/todo.reducer.ts
export enum TodoActionTypes {
AddTodo = '[Todo] Add Todo', UpdateTodo = '[Todo] Update Todo', ...
}
let currentId = 1;
export class AddTodo implements Action {
readonly type = TodoActionTypes.AddTodo;
constructor(public payload: { todo: Todo }) {
payload.todo.id = currentId++;
}
}
export type TodoActions = LoadTodos | AddTodo | UpsertTodo | AddTodos
| UpsertTodos | UpdateTodo | UpdateTodos | DeleteTodo | DeleteTodos | ClearTodos;
Todos Model
// src/app/reducers/todo/todo.model.ts
import { EntityState } from '@ngrx/entity';
export interface Todo {
id: number;
completed: boolean;
text: string;
}
export interface Todos {
todos: EntityState<Todo>;
}
Composing Reducers
import * as todos from './todo/todo.reducer';
import * as currentFilter from './currentFilter/currentFilter.reducer';
export interface Filter {
currentFilter: string;
}
export interface Todos {
todos: EntityState<Todo>;
}
export interface TodosState extends Todos, Filter { }
export const reducers: ActionReducerMap<TodosState> = {
todos: todos.reducer,
currentFilter: currentFilter.reducer
};
New Pipeable Selectors
// ngrx v4
// export const getTodos = state$ => state$.select(s => s.todos);
// export const getCurrentFilter = state$ => state$.select('currentFilter');
export const getTodos = state$ => state$.pipe(
select('todos'),
map(todoEntity.selectAll)
);
export const getCurrentFilter = state$ => state$.pipe(
select('currentFilter')
);
AppModule Setup
import { StoreModule } from "@ngrx/store";
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { AppComponent } from './app.component';
import { reducers, metaReducers } from './reducers';
@NgModule({
imports: [
BrowserModule,
StoreModule.forRoot(reducers, { metaReducers }),
!environment.production ? StoreDevtoolsModule.instrument() : [],
],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule {}
Let's use it!
Adding a new Todo
- Component subscribes
- Component dispatches TodoActionTypes.AddTodo
- Store executes rootReducer
- Store notifies Component
- View updates
1
2
3
4
5
Subscribing to the Store
@Component({
template: `
<todo *ngFor="let todo of todos | async | visibleTodos:currentFilter">
{{todo.text}}
</todo>`
})
export class App implements OnDestroy {
constructor(private _store: Store<TodosState>) { }
public ngOnInit() {
this.todos = this._store.let(getTodos);
this._store.let(getCurrentFilter).subscribe((filter: string) => {
this.currentFilter = filter;
})
}
}
app.component.ts
@Component({
template: `<input #todo (keydown.enter)="addTodo(todo1)">`
})
export class AppComponent {
private addTodo(input) {
if (input.value.length === 0) return
const todo: Todo = {
id: null,
text: input.value,
completed: false,
}
this._store.dispatch(new todoActions.AddTodo({ todo }))
input.value = ''
}
}
TodoActionTypes.AddTodo
// add new todo
{
type: '[Todo] Add Todo',
payload: {
todo: {
id: 1,
text: 'Learn Dutch',
completed: false
}
}
}
todos Reducer
export const adapter: EntityAdapter<Todo> = createEntityAdapter<Todo>();
export let initialState: State = adapter.getInitialState();
export function reducer(state = initialState, action: TodoActions): State {
switch (action.type) {
case TodoActionTypes.AddTodo: {
return adapter.addOne(action.payload.todo, state);
}
...
default: { return state; }
}
}
export const {
selectIds, selectEntities, selectAll, selectTotal
} = adapter.getSelectors();
State
{
todos: {
ids: [ 1 ],
entities: {
'1': {
id: 1,
text: 'Learn Dutch',
completed: false
}
}
},
currentFilter: 'SHOW_ALL'
}
More
@MikeRyanDev
@robwormald
@brandontroberts
Rob Wormald
Mike Ryan
Brandon Roberts
@toddmotto
Todd Motto
gsans/ngrx-entity-todo-app
Why nobody told me about ngrx/entity?
By Gerard Sans
Why nobody told me about ngrx/entity?
In this talk we are going to explore one of the latest additions to the ngrx suite. This is the ngrx/entity! We will use an existing ngrx Application to identify repetitive code and show how we can use ngrx/entity to keep our reducers clean and shiny!
- 3,663