Gerard Sans | Axiom 🇬🇧 PRO
Founder of Axiom Masterclass, professional trainings // Forging skills for the new era of AI. GDE in AI, Cloud & Angular. Building London's tech & art nexus @nextai_london. Speaker | MC | Trainer.
by Gerard Sans | @gerardsans
Spoken at 70 events in 23 countries
900
1.4K
Angular Academy
ngrx
Redux
Dan Abramov
source: blog
1
2
3
4
5
let selectedUsers = [1, 2, 3];
let user = { id: 4, username: 'Spiderman'};
let newSelection = selectedUsers.concat([4, 5, 6]); // [1, 2, 3, 4, 5, 6];
let newUser = Object.assign({}, user, { admin: true });
console.log(newUser.admin) // true
// NOT VALID
selectedUsers.push(4, 5, 6); // reference to 'selectedUsers' still the same
user.admin = true; // reference to 'user' still the samelet selectedUsers = Immutable.List([1, 2, 3]);
let user = Immutable.Map({ id: 4, username: 'Spiderman'}):
let newSelection = selectedUsers.push(4, 5, 6); // [1, 2, 3, 4, 5, 6];
let newUser = user.set('admin', true);
newUser.get('admin') // true// function foo(x) { return x+1; }
let foo = x => x+1;
// pure function
foo(1); // 2
foo(1); // 2let flag = false;
let foo = x => {
flag = !flag; // side effect
return flag ? x+1: 0;
}
// not pure function
foo(1); // 2
foo(1); // 0source: blog
1
2
3
4
5
A
A
<app>
<add-todo>
<input><button>Add todo</button>
</add-todo>
<todo-list>
<ul>
<todo id="0" completed="false"><li>buy milk</li></todo>
</ul>
</todo-list>
<filters>
Show: <filter-link><a>All</a><filter-link> ...
</filters>
</app>import { App } from './app';
import { StoreModule } from "@ngrx/store";
import { rootReducer } from './rootReducer';
@NgModule({
imports: [
BrowserModule,
StoreModule.forRoot(rootReducer)
],
declarations: [ App ],
bootstrap: [ App ]
})
export class AppModule {}
platformBrowserDynamic().bootstrapModule(AppModule);1
2
3
4
5
@Component({
template:
`<todo *ngFor="let todo of todos | async">{{todo.text}}</todo>`
})
export class App implements OnDestroy {
public todos: Observable<Todo>;
constructor(
private _store: Store<TodosState>
) {
this.todos = _store.let(
state$ => state$.select(s => s.todos);
);
}
}// add new todo
{
type: ADD_TODO,
id: 1,
text: "learn redux",
completed: false
}const initialState = [];
const todos = (state = initialState, action:Action) => {
switch (action.type) {
case TodoActions.ADD_TODO:
return state.concat({
id: action.id,
text: action.text,
completed: action.completed });
default: return state;
}
}
// {
// todos: [], <-- todos reducer will mutate this key
// currentFilter: 'SHOW_ALL'
// }const currentFilter = (state = 'SHOW_ALL', action: Action) => {
switch (action.type) {
case TodoActions.SET_CURRENT_FILTER:
return action.filter
default: return state;
}
}
// {
// todos: [],
// currentFilter: 'SHOW_ALL' <-- filter reducer will mutate this key
// }import { ActionReducerMap } from '@ngrx/store';
interface Todo {
id: number,
text: string,
completed: boolean
}
export interface TodosState {
todos: Array<Todo>;
currentFilter: string;
}
export const rootReducer: ActionReducerMap<TodosState> = {
todos: todos,
currentFilter: currentFilter
};
{
todos: [{
id: 1,
text: "learn redux",
completed: false
}],
currentFilter: 'SHOW_ALL'
}
// {
// todos: [], <-- we start with no todos
// currentFilter: 'SHOW_ALL'
// }import { EffectsModule } from '@ngrx/effects';
import { todoEffects } from './todoEffects';
@NgModule({
imports: [
StoreModule.forRoot(rootReducer),
EffectsModule.forRoot([todoEffects]),
]
})
export class AppModule {}import { Actions, Effect, ofType } from '@ngrx/effects';
import { TodoActions, ADD_TODO_EFFECT } from './todoActions';
@Injectable()
export class todoEffects {
constructor(
private actions$ : Actions,
private actions : TodoActions ) { }
@Effect()
addTodoAsync$ = this.actions$
.ofType(ADD_TODO_EFFECT)
.mergeMap(action => this.http.post('/todo', action.text)
.map(response => ({
type: 'ADD_TODO_SUCCESS',
text: text
}))
)
}// Dispatch
private addTodoAsync(input) {
this._store.dispatch({
type: ADD_TODO_EFFECT
text: input.value
});
input.value = '';
}
// Middleware
@Effect()
addTodoAsync$ = this.actions$
.ofType(ADD_TODO_EFFECT)
.mergeMap(action => this.http.post('/todo', action.text)
.map(response => ({
type: 'ADD_TODO_SUCCESS',
text: text
}))
)@Injectable()
export class todoEffects {
@Effect()
addTodoAsync$ = this.actions$
.ofType(ADD_TODO_EFFECT)
.mergeMap(action => this.http.post('/todo', action.text)
.map(response => ({
type: 'ADD_TODO_SUCCESS',
text: text
}))
.catch(error => Observable.of({
type: 'ADD_TODO_FAILED',
error: { message: error }
})
)
)
}> 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// 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;// 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;
}
}// 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;// 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>;
}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
};// 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')
);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 {}@MikeRyanDev
@robwormald
@brandontroberts
@toddmotto
By Gerard Sans | Axiom 🇬🇧
In this talk we are going to use the latest version of Angular and ngrx (v5) to learn advanced state management. We will introduce Redux principles before moving into ngrx/store, ngrx/effects for asynchronous actions, ngrx/entity and ngrx/schematics. ngrx/store uses an implementation inspired by Redux but using Observables from RxJS 5.5. We will also cover best practices and how to implement error handling.
Founder of Axiom Masterclass, professional trainings // Forging skills for the new era of AI. GDE in AI, Cloud & Angular. Building London's tech & art nexus @nextai_london. Speaker | MC | Trainer.