Loiane Groner
Java + JavaScript/HTML5 developer • Web/Angular GDE • Microsoft MVP • author @PacktPub More decks available at: https://www.slideshare.net/loianeg
Service
Component
Service
Component
Service
Component
Service
Component
Service
Component
Service
Component
Service
Component
Component
Component
Service
View
Action
Store
current state
iteraction
dispatch action
reducer
state / action
new state
> ng new angular-ngrx-example
> npm install @ngrx/{store, effects, entity, store-devtools} --save
import { StoreRouterConnectingModule } from '@ngrx/router-store';
import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
// ...
@NgModule({
imports: [
StoreModule.forRoot(reducers),
EffectsModule.forRoot([]),
StoreRouterConnectingModule,
!environment.production ? StoreDevtoolsModule.instrument({ maxAge: 50 }) : []
]
})
// ...
@NgModule({
imports: [
StoreModule.forFeature('task', taskReducer),
EffectsModule.forFeature([TaskEffects])
]
})
export class TasksModule {}
A plain JavaScript object
{
tasks: [
{
id: '1',
title: 'Task 01',
completed: false
},
{
id: '2',
title: 'Task 02',
completed: true
}
],
isLoading: false,
error: null
}
// step 1: define state and initial state
export interface TaskState {
tasks: Task[];
isLoading: boolean;
error: any;
}
export const taskInitialState: TaskState = {
tasks: [],
isLoading: true,
error: null
};
// selectors - select an information from the state
export const taskState = createFeatureSelector<TaskState>('task');
export const selectedRecords = createSelector(taskState, (state: TaskState) => state.tasks);
export const selectIsLoading = createSelector(taskState, (state: TaskState) => state.isLoading);
Request for changing the sate
What's the component responsability?
export enum TaskActionTypes {
LOAD = '[Task] LOAD Requested',
CREATE = '[Task] CREATE Requested',
UPDATE = '[Task] UPDATE Requested',
REMOVE = '[Task] REMOVE Requested',
ERROR = '[Task] Error'
}
What information needs to be included in each Action?
export class CreateAction implements Action {
type = TaskActionTypes.CREATE;
constructor(public payload: { task: Task }) { }
}
A pure function that accepts a state and an action and returns a new state
export const taskReducer: ActionReducer<Task[]> =
(state: Task[] = [], action: TaskAction) => {
switch (action.type) {
case TaskActionTypes.CREATE:
return [...state, action.payload.task];
case TaskActionTypes.REMOVE:
return state.filter((task: Task) => {
return task.id !== action.payload.task.id;
});
default:
return state;
}
};
export class TasksComponent implements OnInit {
constructor(private store: Store<AppState>) {}
ngOnInit() {
this.store.dispatch(new Action.LoadAction());
}
);
export class TasksComponent implements OnInit {
tasks$: Observable<Task[]>;
constructor(private store: Store<AppState>) {}
ngOnInit() {
this.store.dispatch(new Action.LoadAction());
this.tasks$ = this.store.select(State.selectedRecords)
}
);
export const taskState = createFeatureSelector<TaskState>('task');
export const selectedRecords = createSelector(taskState, (state: TaskState) => state.tasks);
<mat-card>
<app-task-form (createTask)="onCreateTask($event)"></app-task-form>
<mat-spinner *ngIf="isLoading$ | async; else taskList"></mat-spinner>
<ng-template #taskList>
<app-tasks-list
[tasks]="tasks$"
(remove)="onRemoveTask($event)"
(edit)="onUpdateTask($event)">
</app-tasks-list>
</ng-template>
<div class="error-msg" *ngIf="error$ | async as error">
<p>{{ error }}</p>
</div>
</mat-card>
@Component({
selector: 'app-task-item',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TaskItemComponent {
@Input() task: Task;
@Output() remove: EventEmitter<any> = new EventEmitter(false);
@Output() edit: EventEmitter<any> = new EventEmitter(false);
onRemove() {
this.remove.emit();
}
onEdit() {
this.edit.emit(this.task);
}
}
Request for changing the sate
What's the component responsability?
export enum TaskActionTypes {
LOAD = '[Task] LOAD Requested',
LOAD_SUCCESS = '[Task] LOAD Success',
CREATE = '[Task] CREATE Requested',
CREATE_SUCCESS = '[Task] CREATE Success',
UPDATE = '[Task] UPDATE Requested',
UPDATE_SUCCESS = '[Task] UPDATE Success',
REMOVE = '[Task] REMOVE Requested',
REMOVE_SUCCESS = '[Task] REMOVE Success',
ERROR = '[Task] Error'
}
Make the request and wait for the response to complete the action
@Effect()
createAction$ = this.actions$.pipe(
ofType<Action.CreateAction>(Action.TaskActionTypes.CREATE),
map(action => action.payload),
mergeMap(payload =>
this.api.create(payload.task).pipe(
map(res => new Action.CreateActionSuccess({ task: res })),
catchError(error => this.handleError(error)))
));
export interface EntityState<T> {
ids: string[];
entities: Dictionary<T>;
}
export interface EntityStateAdapter<T> {
addOne<S extends EntityState<T>>(entity: T, state: S): S;
addMany<S extends EntityState<T>>(entities: T[], state: S): S;
addAll<S extends EntityState<T>>(entities: T[], state: S): S;
removeOne<S extends EntityState<T>>(key: string, state: S): S;
removeMany<S extends EntityState<T>>(keys: string[], state: S): S;
removeAll<S extends EntityState<T>>(state: S): S;
updateOne<S extends EntityState<T>>(update: Update<T>, state: S): S;
updateMany<S extends EntityState<T>>(updates: Update<T>[], state: S): S;
}
export declare type EntitySelectors<T, V> = {
selectIds: (state: V) => string[];
selectEntities: (state: V) => Dictionary<T>;
selectAll: (state: V) => T[];
selectTotal: (state: V) => number;
};
case TaskActions.LOAD_SUCCESS: {
return adapter.addMany(action.payload.tasks, state);
}
case TaskActions.CREATE_SUCCESS: {
return adapter.addOne(action.payload.task, state);
}
case TaskActions.UPDATE_SUCCESS: {
return adapter.updateOne(action.payload.task, state);
}
case TaskActions.UPSERT_SUCCESS: {
return adapter.upsertMany(action.payload.tasks, state);;
}
> npm install @ngrx/schematics --save-dev
> ng generate store State --root --module app.module.ts --collection @ngrx/schematics
> ng generate entity tasks/store/Task --module tasks/tasks.module.ts --collection @ngrx/schematics
> ng generate effect tasks/store/Task --module tasks/tasks.module.ts --collection @ngrx/schematics
Main setup
Feature module +
store feature module
By Loiane Groner
In this talk, you will learn how to manage state in an Angular application using the ngrx suite (Redux for Angular powered by RxJS) including store, effects, entities, schematics. You will learn when to use ngrx and the pros and cons of adopting this tool in your project.
Java + JavaScript/HTML5 developer • Web/Angular GDE • Microsoft MVP • author @PacktPub More decks available at: https://www.slideshare.net/loianeg